【Pyxel】短いプログラムで遊ぶ②

Pyxel使ってシューティングゲーム作ってみた。

ゲームらしく作ると少し長くなってしまうのはしょうがないか。

リソースは使ってません。
また、プログラムはクラスを使ってません。
お手軽でゲーム作れるのはPyxelの良いところ！
![invdemo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927925/4745123e-a155-dc05-87ce-77817350209c.png)
```
import pyxel
o_dottbl0 = [0x30,0x40,0x21,0x31,0x41,0x51,0x12,0x22,0x32,0x42,0x52,0x62,0x13,0x33,0x43,0x63,0x14,0x24,0x34,0x44,0x54,0x64,0x15,0x65,0x06,0x76,0x17,0x67,0xff]
o_dottbl1 = [0x30,0x40,0x21,0x31,0x41,0x51,0x12,0x22,0x32,0x42,0x52,0x62,0x13,0x33,0x43,0x63,0x14,0x24,0x34,0x44,0x54,0x64,0x25,0x55,0x16,0x66,0x07,0x77,0xff]
o_dottbl2 = [0x70,0x71,0x62,0x72,0x82,0x53,0x63,0x73,0x83,0x93,0x54,0x64,0x74,0x84,0x94,0x35,0x55,0x65,0x75,0x85,0x95,0xb5,0x36,0x46,0x56,0x66,0x76,0x86,0x96,0xa6,0xb6,0x37,0x47,0x57,0x67,0x77,0x87,0x97,0xa7,0xb7,0xff]
o_dottbl3 = [0x01,0x02,0x03,0x04,0x05,0x06,0xff]
o_dottbl4 = [0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xff]
o_dottbl5 = [0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x08,0x18,0x28,0x19,0xff]
o_dottbl6 = [0x11,0x12,0x13,0x14,0x05,0x15,0x25,0x16,0x17,0x18,0x19,0xff]
o_dottbl7 = [0x11,0x02,0x12,0x22,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xff]
o_dottbl8 = [0x21,0x51,0x91,0xc1,0x32,0x62,0x82,0xb2,0x43,0xa3,0x14,0x24,0xc4,0xd4,0x45,0xa5,0x36,0x66,0x86,0xb6,0x27,0x57,0x97,0xc7,0xff]
#-------------------------------------------------------------------------------
# 定数
SCREEN_WIDTH	= 128
SCREEN_HEIGHT	= 128
CWORK_SIZE	=	6
E_MAX		=	5		#インベーダー総数
ET_MAX		=	10		#インベーダーの弾総数
WORK_TOP	=	0
WORK_END	=	0x03 + ( ( E_MAX + ET_MAX + 2 ) * CWORK_SIZE )	#0x02:変数領域最大サイズ
_ass = WORK_TOP
GWK = [WORK_TOP for _ass in range( WORK_END )]	#変数管理(RAM領域)
#キャラクタワーク(CWORK)内訳
id			=	0		#ID
cond  		=	1		#状態フラグ
xpos  		=	2		#X座標
ypos  		=	3		#Y座標
mcnt		=	4		#移動カウンタ
wait		=	5		#待ちカウンタ
#状態フラグ内容
F_LIVE		=	0x01	#bit0 0/1=存在しない/存在する
F_HIT		=	0x02	#bit1 0/1=当たってない/当たった
F_BOMB		=	0x04	#bit2 0/1=爆発してない/爆発中
F_MOVE		=	0x08	#bit3 0/1=右移動/左移動
#-------------------------------------------------------------------------------
#変数
game_status	=	WORK_TOP + 0x00		#ゲームステータス
GS_TITLE	=	0	#タイトル
GS_INIT		=	1	#初期化
GS_GAME		=	2	#ゲーム
GS_GAMEOVER	=	3	#ゲームオーバー
game_score	=	WORK_TOP + 0x01		#スコア保存用
game_wait	=	WORK_TOP + 0x02		#ウエイトカウンタ
#変数領域最大サイズ：0x03
#-------------------------------------------------------------------------------
#ワーク
P_WORK		=	WORK_TOP + 0x03						#プレイヤー
PT_WORK		=	P_WORK + CWORK_SIZE					#プレイヤーの弾、画面内一発
E_WORK		=	PT_WORK + CWORK_SIZE				#インベーダー、最大数：E_MAX
ET_WORK		=	E_WORK + ( E_MAX * CWORK_SIZE )		#インベーダーの弾、最大数：ET_MAX
WORK_MAX	=	ET_WORK + ( ET_MAX * CWORK_SIZE )
#-------------------------------------------------------------------------------
#関数群
#-------------------------------------------------------------------------------
#ドット描画（セロハンモード用）
def dot_put( xp, yp ):
	col = 0
	offset = int( SCREEN_HEIGHT / 7 )
	if( yp < ( offset * 1 ) ):
		col = 7		#WHITE
	elif( ( ( offset * 1 ) <= yp ) and ( yp < ( offset * 2 ) ) ):
		col = 6		#BLUE
	elif( ( ( offset * 2 ) <= yp ) and ( yp < ( offset * 3 ) ) ):
		col = 1		#GREEN
	elif( ( ( offset * 3 ) <= yp ) and ( yp < ( offset * 4 ) ) ):
		col = 2		#MIZUIRO
	elif( ( ( offset * 4 ) <= yp ) and ( yp < ( offset * 5 ) ) ):
		col = 3		#PURPLE
	elif( ( ( offset * 5 ) <= yp ) and ( yp < ( offset * 6 ) ) ):
		col = 4		#YELLOW
	else:	#elif( ( offset * 6 ) <= yp ) ):
		col = 5		#RED
	pyxel.pset(xp, yp, col)
#-------------------------------------------------------------------------------
#ドット削除
def dot_erase( xp, yp ):
	pyxel.pset( xp, yp, 0 )
#-------------------------------------------------------------------------------
#その位置のドットの存在をチェックする
#ret : 0/1 = no(black)/yes(not black)
def dot_check( xp, yp ):
	col = pyxel.pget(xp,yp)
	if col == 0:
		return 0
	else:
		return 1
#-------------------------------------------------------------------------------
#キャラクタセット
#_type=0/1:削除/描画
def dot_pattern( _type, dx, dy, dn ):
	chara_tbl = [
		o_dottbl0,		#タコ１
		o_dottbl1,		#タコ２
		o_dottbl2,		#砲台
		o_dottbl3,		#砲台の弾
		o_dottbl4,		#敵弾１
		o_dottbl5,		#敵弾２
		o_dottbl6,		#敵弾３
		o_dottbl7,		#敵弾４
		o_dottbl8,		#敵やられ
	]

	if( dn < 9 ):		#キャラクタセット総数
		adr = chara_tbl[dn]
		_cnt = 0
		while (adr[_cnt] != 0xff):
			_xp = int( adr[_cnt] / 0x10 )
			_yp = int( adr[_cnt] % 0x10 )
			if( type == 0 ):
				dot_erase(dx + _xp, dy + _yp)
			else:
				dot_put(dx + _xp, dy + _yp)
			_cnt += 1
#-------------------------------------------------------------------------------
#指定された位置にパターン削除します
def dot_pattern_erase( dx, dy, dn ):
	dot_pattern( 0, dx, dy, dn )
#-------------------------------------------------------------------------------
#指定された位置にパターンで置きます
def dot_pattern_put( dx, dy, dn ):
	dot_pattern( 1, dx, dy, dn )
#===============================================================================
#インベーダー初期化
def e_init(_wk):
	_minpos = int( SCREEN_HEIGHT / 7 )
	GWK[_wk + cond] = 0
	GWK[_wk + id] = 0
	GWK[_wk + xpos] = _minpos * (-1)
	GWK[_wk + ypos] = _minpos
	GWK[_wk + mcnt] = pyxel.rndi(_minpos, SCREEN_WIDTH)
	GWK[_wk + wait] = pyxel.rndi(1,_minpos) * E_MAX
#-------------------------------------------------------------------------------
#更新
def update():
	if( GWK[game_status] == GS_TITLE ):
		if getInputA():
			#ゲーム開始
			GWK[game_status] = GS_INIT
		elif getInputB():
			#終了
			pyxel.quit()
	elif( GWK[game_status] == GS_INIT ):
		#初期設定
		GWK[game_score] = 0
		#プレイヤー
		GWK[P_WORK + cond] = F_LIVE
		GWK[P_WORK + id] = 2
		GWK[P_WORK + xpos] = SCREEN_WIDTH/2
		GWK[P_WORK + ypos] = SCREEN_HEIGHT * 7/8
		#インベーダー
		for _cnt in range(E_MAX):
			_wk = E_WORK + ( CWORK_SIZE * _cnt )
			e_init(_wk)
		GWK[game_status] = GS_GAME
	elif( GWK[game_status] == GS_GAME ):
		#プレイヤーの動き
		if( ( GWK[P_WORK + cond] & ( F_LIVE + F_BOMB ) ) == ( F_LIVE + F_BOMB ) ):
			#爆発中
			GWK[P_WORK + wait] -= 1
			if( GWK[P_WORK + wait] < 0 ):
				GWK[P_WORK + cond] = 0
				#ゲームオーバーセット
				GWK[game_status] = GS_GAMEOVER
				GWK[game_wait] = 300
		elif( ( GWK[P_WORK + cond] & ( F_LIVE + F_HIT ) ) == ( F_LIVE + F_HIT ) ):
			#当たったら爆発
			GWK[P_WORK + cond] |= F_BOMB
			GWK[P_WORK + id] = 8
			GWK[P_WORK + wait] = 4
		elif( GWK[P_WORK + cond] & F_LIVE ):
			if getInputA():
				#弾発射：プレイヤーの弾発生
				if( ( GWK[PT_WORK + cond] & F_LIVE ) == 0 ):
					GWK[PT_WORK + cond] = F_LIVE
					GWK[PT_WORK + id] = 3
					GWK[PT_WORK + xpos] = GWK[P_WORK + xpos] + 7
					GWK[PT_WORK + ypos] = GWK[P_WORK + ypos]
			if getInputRIGHT():
				#右移動
				GWK[P_WORK + xpos] += 1
				if( GWK[P_WORK + xpos] > ( SCREEN_WIDTH - 0x10 ) ):
					GWK[P_WORK + xpos] = ( SCREEN_WIDTH - 0x10 )
			elif getInputLEFT():
				#左移動
				GWK[P_WORK + xpos] -= 1
				if( GWK[P_WORK + xpos] < 0 ):
					GWK[P_WORK + xpos] = 0
		#プレイヤーの弾の動き
		if( GWK[PT_WORK + cond] & F_LIVE ):
			GWK[PT_WORK + ypos] -= 4
			if( GWK[PT_WORK + ypos] < (-8) ):
				GWK[PT_WORK + cond] = 0
		#インベーダーの動き
		for _cnt in range(E_MAX):
			_wk = E_WORK + ( CWORK_SIZE * _cnt )
			if( ( GWK[_wk + cond] & F_LIVE ) == 0 ):
				#登場待ち
				GWK[_wk + wait] -= 1
				if( GWK[_wk + wait] < 0 ):
					GWK[_wk + cond] = F_LIVE
					GWK[_wk + wait] = pyxel.rndi( 50, 300 )		#弾発射間隔をセット
			elif( ( GWK[_wk + cond] & ( F_LIVE + F_BOMB ) ) == ( F_LIVE + F_BOMB ) ):
				#爆発中
				GWK[_wk + wait] -= 1
				if( GWK[_wk + wait] < 0 ):
					#爆発終わったので得点加算して初期化
					e_init(_wk)
			elif( ( GWK[_wk + cond] & ( F_LIVE + F_HIT ) ) == ( F_LIVE + F_HIT ) ):
				#得点加算
				GWK[game_score] += 10
				#撃たれたら爆発
				GWK[_wk + cond] |= F_BOMB
				GWK[_wk + id] = 8
				GWK[_wk + wait] = 4
				GWK[_wk + xpos] -= 4	#爆発サイズの中心を合わせる
			elif( GWK[_wk + cond] & F_LIVE ):
				_change = 0
				if( GWK[_wk + cond] & F_MOVE ):
					GWK[_wk + xpos] -= 1
					if( GWK[_wk + xpos] < 10 ):
						GWK[_wk + xpos] = 10
						GWK[_wk + cond] &= ~F_MOVE
						_change = 1
				else:
					GWK[_wk + xpos] += 1
					if( GWK[_wk + xpos] > SCREEN_WIDTH-10-8 ):
						GWK[_wk + xpos] = SCREEN_WIDTH-10-8
						GWK[_wk + cond] |= F_MOVE
						_change = 1
				GWK[_wk + mcnt] -= 1
				if( GWK[_wk + mcnt] < 0 ):
					GWK[_wk + mcnt] = pyxel.rndi(20, SCREEN_WIDTH)
					
					if( GWK[_wk + cond] & F_MOVE ):
						GWK[_wk + cond] &= ~F_MOVE
					else:
						GWK[_wk + cond] |= F_MOVE
					_change = 1
				if( _change ):
					GWK[_wk + ypos] += 8
					if( GWK[_wk + ypos] > SCREEN_HEIGHT ):
						#Y方向フレームアウト
						e_init(_wk)
				#ヒットチェック
				if( GWK[_wk + cond] & F_LIVE ):
					#プレイヤーとインベーダーのヒットチェック
					#プレイヤーの中心座標
					_px = GWK[P_WORK + xpos] + 7
					_py = GWK[P_WORK + ypos] + 4
					#インベーダーの中心座標
					_ex = GWK[_wk + xpos] + 4
					_ey = GWK[_wk + ypos] + 4
					#プレイヤーとインベーダーの距離を出して当たる距離内ならヒットセット
					_k = pyxel.sqrt( ( _ex - _px ) * ( _ex - _px ) + ( _ey - _py ) * ( _ey - _py ) )
					if( _k < 8 ):
						#インベーダーヒット
						GWK[_wk + cond] |= F_HIT
						#プレイヤーヒット
						GWK[P_WORK + cond] |= F_HIT
					#プレイヤーの弾とインベーダーのヒットチェック
					#プレイヤーの弾の上先端座標
					_px = GWK[PT_WORK + xpos]
					_py = GWK[PT_WORK + ypos]
					#プレイヤーの弾とインベーダーの距離を出して当たる距離内ならヒットセット
					_k = pyxel.sqrt( ( _ex - _px ) * ( _ex - _px ) + ( _ey - _py ) * ( _ey - _py ) )
					if( _k < 4 ):
						#インベーダーヒット
						GWK[_wk + cond] |= F_HIT
						#プレイヤーの弾はヒットした瞬間消す
						GWK[PT_WORK + cond] = 0
				#インベーダーが生きているなら弾を出す
				if( GWK[_wk + cond] & F_LIVE ):
					#弾発射間隔を減算
					GWK[_wk + wait] -= 1
					if( GWK[_wk + wait] < 0 ):
						GWK[_wk + wait] = pyxel.rndi( 50, 300 )		#次回の弾発射間隔をセット
						#弾発射：インベーダーの弾発生
						for _cnt_et in range(ET_MAX):
							_wk_et = ET_WORK + ( CWORK_SIZE * _cnt_et )
							if( ( GWK[_wk_et + cond] & F_LIVE ) == 0 ):
								GWK[_wk_et + cond] = F_LIVE
								GWK[_wk_et + id] = 4
								GWK[_wk_et + xpos] = GWK[_wk + xpos] + 2
								GWK[_wk_et + ypos] = GWK[_wk + ypos] + 3
								break
		#インベーダーの弾の動き
		for _cnt in range(ET_MAX):
			_wk_et = ET_WORK + ( CWORK_SIZE * _cnt )
			if( GWK[_wk_et + cond] & F_LIVE ):
				GWK[_wk_et + ypos] += 1
				if( GWK[_wk_et + ypos] > SCREEN_HEIGHT ):
					GWK[_wk_et + cond] = 0
				else:
					#インベーダーの弾とプレイヤーのヒットチェック
					if( GWK[P_WORK + cond] & F_LIVE ): 
						#プレイヤーの中心座標
						_px = GWK[P_WORK + xpos] + 7
						_py = GWK[P_WORK + ypos] + 4
						#インベーダーの弾の下先端座標
						_ex = GWK[_wk_et + xpos] + 1
						_ey = GWK[_wk_et + ypos] + 9
						_k = pyxel.sqrt( ( _ex - _px ) * ( _ex - _px ) + ( _ey - _py ) * ( _ey - _py ) )
						if( _k < 4 ):
							#インベーダーの弾ヒットしたら消滅
							GWK[_wk_et + cond] = 0
							#プレイヤーヒット
							GWK[P_WORK + cond] |= F_HIT
	elif( GWK[game_status] == GS_GAMEOVER ):
		#ゲームオーバー
		GWK[game_wait] -= 1
		if( GWK[game_wait] < 0 ):
			#ワーククリア
			for _cnt in range( P_WORK, WORK_END - P_WORK ):
				GWK[_cnt] = 0
			GWK[game_status] = GS_TITLE
#-------------------------------------------------------------------------------
#描画
def draw():
	pyxel.cls(0)

	#プレイヤーの弾を表示
	if( GWK[PT_WORK + cond] & F_LIVE ):
		dot_pattern_put( GWK[PT_WORK + xpos], GWK[PT_WORK + ypos], GWK[PT_WORK + id] )

	#プレイヤーを表示
	if( GWK[P_WORK + cond] & F_LIVE ):
		dot_pattern_put( GWK[P_WORK + xpos], GWK[P_WORK + ypos], GWK[P_WORK + id] )

	#インベーダーを表示
	for _cnt in range(E_MAX):
		_wk = E_WORK + ( CWORK_SIZE * _cnt )
		if( ( GWK[_wk + cond] & ( F_LIVE + F_BOMB ) ) == ( F_LIVE + F_BOMB ) ):
			dot_pattern_put( GWK[_wk + xpos], GWK[_wk + ypos], GWK[_wk + id] )
		elif( GWK[_wk + cond] & F_LIVE ):
			_an = ( pyxel.frame_count >> 3 ) & 1
			dot_pattern_put( GWK[_wk + xpos], GWK[_wk + ypos], GWK[_wk + id] + _an )

	#インベーダーの弾を表示
	for _cnt_et in range(ET_MAX):
		_wk_et = ET_WORK + ( CWORK_SIZE * _cnt_et )
		if( GWK[_wk_et + cond] & F_LIVE ):
			_an = ( pyxel.frame_count >> 3 ) & 0x03
			dot_pattern_put( GWK[_wk_et + xpos], GWK[_wk_et + ypos], GWK[_wk_et + id] + _an )

	#スコア表示
	pyxel.text( SCREEN_WIDTH/2 - (4*10/2), 0, 'SCORE ' + str(GWK[game_score]), 7 )
	#テキスト表示
	if( GWK[game_status] == GS_TITLE ):
		pyxel.text( SCREEN_WIDTH/2 - (4*5/2), SCREEN_HEIGHT/4, 'TITLE', 7 )
		pyxel.text( SCREEN_WIDTH/2 - (4*26/2), SCREEN_HEIGHT/4 * 3, 'Z-KEY OR A-BUTTON IS START', 7 )
		pyxel.text( SCREEN_WIDTH/2 - (4*26/2), SCREEN_HEIGHT/4 * 3 + 6, 'X-KEY OR B-BUTTON IS EXIT ', 7 )
	elif( GWK[game_status] == GS_GAMEOVER ):
		pyxel.text( SCREEN_WIDTH/2 - (4*8/2), SCREEN_HEIGHT/2, 'GAMEOVER', 7 )
#-----------------------------------------------------------------
#入力（キーボード＆ジョイパッド）

#上
def getInputUP():
	if pyxel.btn(pyxel.KEY_UP) or pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_UP):
		return 1
	else:
		return 0
#下
def getInputDOWN():
	if pyxel.btn(pyxel.KEY_DOWN) or pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_DOWN):
		return 1
	else:
		return 0
#左
def getInputLEFT():
	if pyxel.btn(pyxel.KEY_LEFT) or pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_LEFT):
		return 1
	else:
		return 0
#右
def getInputRIGHT():
	if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_RIGHT):
		return 1
	else:
		return 0
#button-A（決定）
def getInputA():
	if pyxel.btnp(pyxel.KEY_Z) or pyxel.btnp(pyxel.GAMEPAD1_BUTTON_A):
		return 1
	else:
		return 0
#button-B（キャンセル）
def getInputB():
	if pyxel.btnp(pyxel.KEY_X) or pyxel.btnp(pyxel.GAMEPAD1_BUTTON_B):
		return 1
	else:
		return 0
#-----------------------------------------------------------------
pyxel.init( SCREEN_WIDTH, SCREEN_HEIGHT, fps=60 )
#パレット変更
pyxel.colors[0] = 0x000000		#BLACK
pyxel.colors[1] = 0x00FF00		#GREEN
pyxel.colors[2] = 0x00FFFF		#MIZUIRO
pyxel.colors[3] = 0xFF00FF		#PURPLE
pyxel.colors[4] = 0xFFFF00		#YELLOW
pyxel.colors[5] = 0xFF0000		#RED
pyxel.colors[6] = 0x0000FF		#BLUE
pyxel.colors[7] = 0xFFFFFF		#WHITE
#ワーククリア
for _cnt in range( WORK_END - WORK_TOP ):
	GWK[_cnt] = 0
#実行
pyxel.run( update, draw )
```
[動作確認はこちら](https://sanbunnoichi1962.web.fc2.com/pyxel_experiment.html#invdemo)