buto > /dev/null

だいたい急に挑戦してゴールにたどり着かずに飽きる日々です

pyxel アイテムゲットを実装

前回は画面を右から左に流れるアイテムを実装しました pyxel ゲーム画面にアイテムを流す

今回のソースコードはこちら https://github.com/butorisa/pretty-hopper/tree/debug

アイテムゲット時の動きを実装

キャラが流れてくるアイテムと衝突したら「衝突したアイテムが消える」ようにする

class Skill(Enum):
    """ 画面に流れるオブジェクト """
    Java = auto()
    Python = auto()
    Ruby = auto()

    def __init__(self, num):
        self.active = True
        self.position = [(i * 60, randint(0, 104)) for i in range(4)]

アイテムオブジェクトのクラス 衝突時に self.active をFalseにすれば消える

def switch_active(self, obj_x, obj_y, target_x, target_y, is_activate):
    """ オブジェクトが重なったら一方を非表示にする """
    if not is_activate:
        return False

    if abs(obj_x - target_x) < 10 and abs(obj_y - target_y) < 10:
        if self.game_mode == GameMode.Town:
            self.player.position = [8, 104]
        elif self.game_mode == GameMode.Office:
            self.score += 10
        return False
    else:
        return True

衝突判定(ここのゲームモードはOffice)戻り値のbooleanをself.activeに代入する

デュークくんをゲットすると一斉に消えるバグ

f:id:butorisa:20201020174333g:plain

(デュークくんだけではないですが)ある1つのアイテムをゲットすると画面上にあるすべてのアイテムが消えてしまいました

画面に流れるアイテムが何なのかを分かっていないとこんなバグが生まれます

1つのアイテム = リストの1要素

アイテムは self.skill に格納している この中身が複雑だった…(自分が作ったが)

self.skill:[Java, Python, Ruby] Enumクラスのリスト
┃
┗ Java:Enumクラス
    ┃
    ┗ self.state:デュークくんリスト リストの長さ=デュークくんオブジェクト数
        ┃
        ┗ self.stateの1要素:( x座標, y座標, 表示フラグ )

画面上の1つのデュークくんは上記のself.stateの1要素

バグの原因は「Enumクラス自体に表示フラグを付けていた」こと キャラがアイテムと衝突した時に表示フラグをFalseにすると、Enumクラス以下が非表示になるので self.state にいる「アイテムオブジェクトすべて」が消えてしまっていた

1デュークくんごとに表示フラグを設定

バグを解消するには self.state の要素ごとに表示フラグを設定する

class Skill(Enum):
    """ 画面に流れるオブジェクト """
    Java = auto()
    Python = auto()
    Ruby = auto()

    def __init__(self, num):
        # [x, y, is_active]
        self.state = [(i * 60, randint(0, 104), True) for i in range(4)]

__init__()( x座標, y座標, 表示フラグ ) を持つ長さ4のリストを設定する

f:id:butorisa:20201020174440g:plain

これでゲットしたアイテムだけが消えるようになりました^^

カイジで条件付き確率を学ぶ

カイジにハマっている間にどんどん統計を学んじゃおう! 前回は地下チンチロから3つのサイコロで出目になる確率を求めた(反復試行) カイジで確率(ラプラスの定義)を学ぶ

eカードで条件付き確率っ…!

eカードのルール 怖すぎてちゃんと見られてないから間違ってるかも - カードを互いに出してその強さで勝敗がつく(同じカードの時は引き分け) - 手持ちのカードは5枚 - 皇帝:1枚(勝ち→市民 負け→奴隷) - 市民:3枚(勝ち→奴隷 負け→皇帝) - 奴隷:1枚(勝ち→皇帝 負け→市民) - 引き分けのカードは手元には戻さない

1回目に市民、2回目に皇帝を出す確率

2枚目に皇帝は通せないはずだっ…などの心理戦は考慮しちゃダメ

f:id:butorisa:20201020174143j:plain

2つの事象がどちらも起こること 積事象 を求める おしゃれベン図はCanvaで作ったよ! - 1回目に市民を出す確率 = 3/5 - 2回目に皇帝を出す確率 = 1/4 1回目のカードは戻さないから手持ちのカードは4枚

ベン図からも「積事象」って呼び方からも 2つの事象を掛け合わせればOK!

1回目に市民、2回目に皇帝を出す確率は… 3/5 × 1/4 = 3/20 となる

乗法定理!乗法定理!!乗法定理!!!

P(A∩B) = P(A) × P(B) 事象AとBどちらも起こる確率は「Aが起きる確率×Bが起きる確率」で求められる

式変形で2回目に皇帝を出す確率を求める

今度は「1回目に市民を出した時(事象A)、2回目に皇帝を出す(事象B)確率」を求める

乗法定理の式を変形すると… P(B) = P(A∩B) / P(A) となる

(3/20) / (3/5) = 5/20 = 1/4 当たり前だけど、上記と同じ値になってる!

ちなみに私は分数の分数ができません 分母と分子を入れ替えて掛け算にしています

これは運否天賦などではないっ…、圧倒的ベイズの定理!

ベイズの定理です 圧倒的ベイズなんて言葉はありません

さっき乗法定理を変形した P(B) = P(A∩B) / P(A)ベイズの定理と言うそうです

@kumiizoo さんがベイズの定理を丁寧に解説してくれています 【統計学勉強ノート】その2〜ベイズの定理〜

カイジで確率(ラプラスの定義)を学ぶ

最近Huluを入れてカイジを見ております(シーズン1は悪魔的すぎて途中でやめた) シーズン2で地下の強制労働場でのチンチロリンのシーンで確率の話があったので計算してみる!

チンチロリン(チンチロ)とは

ニコニコ大百科 - チンチロリン

チンチロリンとは、サイコロを使って行うギャンブルゲームである。

子はその回に賭けるチップを張る。 親がサイコロをどんぶりに振り、「出目2~5」以外の出来役は即勝ちもしくは即負けなので清算する。 「出目2~5」ならば子が順にサイコロを振り、出来役に応じて清算する。

f:id:butorisa:20201020173952j:plain

居酒屋でハイボールのサービスとかにも使われてるみたい

3つのサイコロを振って出目になる確率

チンチロのルールはこれ(アニメを思い出して書いています) - 3つのサイコロのうち「2つ以上が同じ数字」になっていれば出目→勝負ができる! - 出目にならなくても「3回まで」はサイコロを振れる - 3回振っても出目にならなかったら負け

まず出目を出さないと始まらない!!

出目になるのは何通り?

サイコロの目は1〜6の6通り ※通常のサイコロ サイコロは3つだから出る目は全部で…6の3乗=「216通り」

出目の確率は「2つのサイコロが同じ数字の確率」+「3つのサイコロが同じ数字の確率」 - 2つのサイコロが同じ数字になる確率 - 「1」が2つ→もう1つのサイコロは2〜6のどれかだから「5通り」 - [1, 1, 2~6]、[1, 2~6, 1]、[2~6, 1, 1] のパターンがあるので、5通り×3パターン=「15通り」 - 「2」〜「6」も同様だから、15×6=「90通り」 - 3つのサイコロが同じ数字になる確率 - 6通り!これは簡単

出目になるのは、90+6=「96通り」 出目の確率は「96/216」約分して「4/9」 1回だけサイコロを振る時は9分の4か…

3回振ったうちに出目になる確率

サイコロは3回まで振れるので、出目の確率はもっと上がる! 1回だったら、4/9(約44.4%) 2回だったら、4/9+(4/9×4/9)=36/81+16/81=52/81(約64.2%) 3回だったら、4/9+(4/9×4/9×4/9)=324/729+64/729=388/729(約53.2%)あれ?下がった

※ ここは自力でできずアニメの解説シーンを頼った

ラプラスの定義だっ…!

何気なく確率 = 出目の事象数 / 全事象数 をしていたが、19世紀にラプラスが定義したもの

統計学の時間で確認 9-2. 確率の計算(数え上げ)

アニメ見返しました

アニメでも解説があったので見返してみた カイジ版チンチロの出目は「108通り」もあったらしく、1回のチャレンジですでに50%で出目! 2回目は0.5+(0.5×0.5)=0.5+0.25=0.75(75%) 3回目は0.75+(0.5×0.5×0.5)=0.75+0.125=0.875(87.5%)

出目を出すだけなら結構な高確率だった

pyxel ゲーム画面にアイテムを流す

前回はキャラが家に入ったらローディング画面を表示するところまで pyxel ゲーム画面遷移+動きをつける

今回はゲーム画面にアイテム(オブジェクト?)を流してみる すべてのコード+画像はこちら

f:id:butorisa:20201020173612g:plain

お手本の記事はこちら 【Pythonでゲームを作ろう!】レトロな2Dゲームを作ってみた!

Pyxel Editorでアイテムを描く

プログラミング言語のアイコンたち

f:id:butorisa:20201020173656p:plain f:id:butorisa:20201020173719p:plain f:id:butorisa:20201020173730p:plain

このアイテムをたくさんゲットしてキャラのスキル(スコア)がアップしていくゲームにする予定

アイテムのクラスを定義

class Skill(Enum):
    """ 画面に流れるオブジェクト """
    Java = auto()
    Python = auto()
    Ruby = auto()

    def __init__(self, num):
        self.position = [(i * 60, randint(0, 104)) for i in range(4)]

    def get_image(self, enum):
        """ イメージバンクから切り出す座標を返す """
        if enum == 'Java':
            return 32, 80
        elif enum == 'Python':
            return 48, 80
        elif enum == 'Ruby':
            return 0, 96

アイテムを追加したらEnumを追加する __init__()でアイテムのランダムに初期位置を設定

# ゲームのメインクラス
def __init__(self):
    self.skill = self.create_skill()

def create_skill(self):
    """ skillオブジェクト生成 """
    num = len([obj.name for obj in Skill])
    skills = [Skill(i) for i in range(1, num + 1)]
    return skills

ゲーム本体の初期化処理で全部のEnumクラスを生成してリストで保持している self.skill = [Javaクラスオブジェクト, Pythonクラスオブジェクト...]

アイテムを画面に描画

def draw_object(self):
    """ 流れてくるオブジェクトを描画 """
    for enum in self.skill:
        u, v = enum.get_image(enum.name)
        for x, y in enum.position:
            pyxel.blt(x, y, 0, u, v, 16, 16, 5)

初期化処理で生成したself.skillから要素を1つずつ取り出してpyxel.blt()で描画する 上記のSkillクラスget_image()の引数にEnumクラス名を渡すと対応したアイテム画像の座標が 返ってくる

アイテムが流れるアニメーションをつける

# ゲームのメインクラス
def update(self):
    """ 画面のコントロール """
    # キー操作でキャラ移動する処理なども書かれている

    if self.game_mode == GameMode.Office:
        for enum in self.skill:
            for i, v in enumerate(enum.position):
                enum.position[i] = self.update_object(*v)

ここでもself.skillから取り出したEnumクラスのposition属性に位置座標を追加する

def update_object(self, x, y):
    """ オブジェクトのアニメーション """
    x -= 2
    if x < -40:
        x += 240
        y = randint(0, 104)
    return x, y

位置座標を引数のx座標 - 2して右→左に流れる座標リストが作れる(ここはお手本を写経)

キャラが会社に出勤してゲームスタート!

f:id:butorisa:20201020173748g:plain

pyxel ゲーム画面遷移+動きをつける

前回はpyxelでゲーム画面表示+キャラの移動までできた pyxelであつ森を知ったかぶりする

ソースコードはこちら

家に着いたらローディング画面を表示

キャラが家に着いたらこのローディング画面に遷移させたい

f:id:butorisa:20201020173318p:plain

こちらの記事をお手本にしました たぶんどんな環境でも使えるであろう、ゲーム画面遷移の作り方になります~

class GameMode(Enum):
    """ 画面一覧 """
    # 町
    Town = auto()
    # 家の中
    Home = auto()

画面遷移を制御するEnumを定義

def switch_active(self, obj, target):
    """ オブジェクトが重なったら一方を非表示にする """
    if abs(obj.position[0] - target.position[0]) < 10\
            and abs(obj.position[1] - target.position[1]) < 10:
        obj.active = False
        # ロード画面
        self.game_mode = GameMode.Home
    else:
        obj.active = True
        # ゲーム画面
        self.game_mode = GameMode.Town

元々作っていたキャラが家に着いたらキャラを非表示にする処理 → ゲームモードのEnumを切り替える

def draw(self):
    """ 画面描画 """
    if self.game_mode == GameMode.Town:
        self.draw_town()
    elif self.game_mode == GameMode.Home:
        self.draw_loading()
        # self.draw_home()

def draw_town(self):
    """ 町を描画 """
    # 背景
    pyxel.cls(7)
    pyxel.bltm(0, 0, 0, 0, 0, 16, 16)
    # 家
    pyxel.blt(self.home.position[0], self.home.position[1], 0, 16, 0, 16, 16, 5)
    # うさぎ
    pyxel.blt(40, 80, 0, 48, 0, 16, 16, 5)
    # プレイヤー
    if self.player.active:
        pyxel.blt(self.player.position[0], self.player.position[1], 0, 0, 0, 16, 16, 5)

def draw_home(self):
    """ 家の中を描画 """
    pass

def draw_loading(self):
    """ ロード画面を描画 """
    pyxel.cls(7)
    pyxel.bltm(0, 0, 0, 16, 0, 16, 16)

ゲームモードによって描画する画面を制御

家に着いたらうさぎが表示されるようになった!! レイアウト変更した^^

f:id:butorisa:20201020173340g:plain

ローディング画面のうさちゃんをジャンプさせる

こちらの記事をお手本にしました Python:pyxelで作ったレトロ風タイトル画面

まずうさちゃんジャンプ画像を用意

f:id:butorisa:20201020173403p:plain

def draw_loading(self):
    """ ロード画面を描画 """
    # ウサギがジャンプする画像を表示
    if self.flip:
        tm_x = 32
        tm_y = 0
    else:
        tm_x = 16
        tm_y = 0

    pyxel.cls(7)
    pyxel.bltm(0, 0, 0, tm_x, tm_y, 16, 16)

    if pyxel.frame_count % 15 == 0:
        self.flip = not self.flip

ゲームフレームが15の倍数の時に通常⇔ジャンプ画像を切り替える タイルマップも座標を指定して表示できる ※ self.flip__init__()関数で初期化しています

ローディング画面でうさちゃんがジャンプ!

f:id:butorisa:20201020173419g:plain

pyxelであつ森を知ったかぶりする

最近Twitterではあつ森が流行ってますね ゲームキューブ版はやってたなあ ゲーム始めちゃうと一日中やってしまうのであつ森は買わない!でも正直やりたい… 自分でゲーム作るならOKだよね?プログラミングだし!

ドットでかわいいゲームが作れるpyxel

pyxel公式ドキュメントが丁寧に書かれているので、つまずくことなくインストール完了 (pyenvを入れ直してpython3.8.2の環境にpyxelをインストール)

pyxelで使えるショートカットキーがあるのが親切

ゲームキャラを作る

pyxelに搭載されているPyxel Editorでドット絵が描ける! ターミナルでpyxeleditor ファイル名を実行するとエディタが起動 (ファイル名は拡張子なしでコマンド実行しても自動でつけてくれてた)

f:id:butorisa:20201020172908p:plain

このエディタでもスクリーンショットのショートカットキー使えた

背景を作る

背景はエディタの左上の□が4つある「タイルマップエディタ」で作れる 右下から背景にしたい画像を選択、左側でドラッグすると画像が貼り付けられる

f:id:butorisa:20201020173025p:plain

ゲーム画面を表示する

こちらの記事をお手本にコーディングしました 【Pythonでゲームを作ろう!】レトロな2Dゲームを作ってみた!

コード一式はこちら

draw関数に画面に表示する処理を書く blt()でエディタで作成した画像を表示、bltm()でタイルマップを表示する エディタで作成した.pyxresファイルは1枚の画像なのでblt()の第4、5引数に座標を指定して プレイヤー、家など個々の画像を切り出して画面に表示する ※ 座標は2枚目のエディタ画面のように右側のサムネイルにカーソルを合わせると上部に表示される

def draw(self):
    """ 画面描画 """
    # 背景
    pyxel.cls(7)
    # bltm(x, y, tm, u, v, w, h, [colkey]) colkey指定なし
    pyxel.bltm(0, 0, 0, 0, 0, 16, 16)
    # プレイヤー描画
    if self.player.active:
        # blt(x, y, img, u, v, w, h, [colkey]) colkey=5で藍色を透過
        pyxel.blt(self.player.position[0], self.player.position[1], 0, 0, 0, 16, 16, 5)
    # 家描画
    pyxel.blt(self.home.position[0], self.home.position[1], 0, 16, 0, 16, 16, 5)
    # うさぎ
    pyxel.blt(16, 24, 0, 48, 0, 16, 16, 5)

ゲームキャラを動かす

矢印キーで動くようにしました 「>0」とかは画面からはみ出ない制御です

class Player:
    """ プレイヤーのコントロール """
    def __init__(self):
        self.active = True
        self.position = [48, 47]

    def action(self):
        """ プレイヤー移動 """
        if pyxel.btnp(pyxel.KEY_LEFT) and self.position[0] > 0:
            self.position[0] -= 10
        if pyxel.btnp(pyxel.KEY_RIGHT) and self.position[0] < 110:
            self.position[0] += 10
        if pyxel.btnp(pyxel.KEY_UP) and self.position[1] > 0:
            self.position[1] -= 10
        if pyxel.btnp(pyxel.KEY_DOWN) and self.position[1] < 110:
            self.position[1] += 10

超ロークオリティあつ森の完成

全然あつ森じゃない… 女の子が原っぱ駆け巡ってるだけだわ…

f:id:butorisa:20201020173046g:plain

パッケージ管理ライブラリ pyflowを使えなかった話

パッケージ管理も流行に乗りたい!!

仮想環境自体いらないレベルだけど「新しいでしょ」って言いたいからpyenv卒業! 2020 年の Python パッケージ管理ベストプラクティスを読んで pyflowを推してるっぽかったから早速インストール!!

pyflowインストール

インストールはこちらにアクセス 私はMacなのでzipped Mac binaryをクリックして実行ファイルをダウンロード undefined.jpg 「pyflow」っていう実行ファイルがダウンロードされるのでそれを(とりあえず)ホームディレクトリに配置しておく パス通せば場所はどこでも良さそう

仮想環境を作る

ターミナルを開いて、pyflow new 環境名するとPythonバージョンを聞かれる

~ % pyflow new pretty-hopper
Please enter the Python version for this project: (eg: 3.8)
3.8
Created a new Python project named pretty-hopper

pyxelでゲーム作りたいので3.8と入力してEnter

作成された環境(プロジェクト)はこれ

pretty_hopper
┝ .git
┝ .gitignore
┝ LICENSE
┝ README.md
┝ pretty_hopper
    ┝ __init__.py
┝ pyproject.toml

ライブラリインストール

pyflow install ライブラリでインストールできる

pretty-hopper % pyflow install pyxel      
Automatic installation of Python 3.8 on Mac is currently unsupported. If you'd like to use this version of Python, please install it.

なんと…python3.8はNGだとのこと pyflow resetでプロジェクトを削除してからプロジェクトディレクトリを消して3.7で再作成 そしてリトライ!あれ?エラーが

pretty-hopper % pyflow install pyxel
Found lockfile
⬇ Installing pyxel 1.3.7 ...
Added a console script: install_pyxel_examples
Added a console script: pyxeleditor
Added a console script: pyxelpackager
⬇ Installing pyinstaller 3.6.0 ...
EX PAR: "__pypackages__/3.7/lib/PyInstaller-3.6" bin: "__pypackages__/3.7/.venv/bin"
thread 'main' panicked at 'Problem running setup.py bdist_wheel: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/libcore/result.rs:1188:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

1回エラーが出るとpyflow listとかもエラーになってしまう…闇が深すぎる… ちなみにPython3.7環境を作ってすぐにpyflow install numpyはインストール成功した pip使わない弊害かな

とりあえず、pyxelでゲーム作りたいのでpipenvかpoetryにしよう