PYXEL GUIDE / PUZZLE

Pythonでパズルゲームを作る

公開日: / Pyxel 2.9.9 の公式ドキュメントとソースで確認

パズルゲームは、Pythonでゲーム作りを学ぶのにぴったりのジャンルです。このページでは、パズルの基本の形を小さなサンプルで確かめてから、このサイトのパズル講座32本を難易度別に紹介します。

講座の数
パズルジャンル 32講座(各5ステップ・遊べる完成版つき)
この記事のサンプル
マス目の反転パズル(約70行・追加ファイルなし)
確認したバージョン
Pyxel 2.9.9
マス目の上でプレイヤーと影が動くパズルゲームの画面
このサイトの講座「一歩貸してください」の元の完成版。自分と、1手遅れて動く影を導くマス目のパズルです(元の完成版を自動操作したPyxel描画)。

パズルゲームの基本の形

アクションゲームは、キャラクターが毎フレーム少しずつ動きます。パズルゲームは、キーを押したときだけ状態が1段階進むのが特長です。速さやタイミングより「ルール」が中心なので、1手ずつ結果を確かめながら作れます。Pythonでゲーム作りを学ぶのに、ぴったりのジャンルです。

多くのパズルゲームは、次の5つの部品でできています。

  1. 盤面(状態)を持つ

    どのマスに何があるかを、リストや辞書で覚えます。

  2. 入力を「1手」に変える

    キーを押した瞬間(pyxel.btnp)だけを、1手として受け付けます。

  3. その手が打てるかを調べる

    壁や盤の外なら、何も変えません。

  4. 盤面を変えて、クリアを調べる

    変えたあとで、クリアの条件を満たしているかを確かめます。

  5. 1手前に戻せるようにする(Undo)

    盤面を変える前に、今の盤面を保存しておきます。

小さな反転パズルを作る

5×5のマスのうち、明るいマスをすべて消せばクリアのパズルです。カーソルのあるマスで Space キーを押すと、そのマスと上下左右のマスが反転(明るい ⇔ 暗い)します。矢印キーでカーソルを動かし、Z キーで1手戻し、R キーで最初からやり直せます。

flip_puzzle.py / 全文
import pyxel

SIZE = 5
CELL = 16
LEFT = 40
TOP = 20
START_MOVES = [(1, 1), (3, 1), (2, 2), (1, 3), (3, 3)]


class App:
    def __init__(self):
        pyxel.init(160, 120, title="Flip Puzzle")
        self.reset()
        pyxel.run(self.update, self.draw)

    def reset(self):
        self.board = [[False] * SIZE for _ in range(SIZE)]
        for x, y in START_MOVES:
            self.flip(x, y)
        self.cx = 2
        self.cy = 2
        self.moves = 0
        self.history = []

    def flip(self, x, y):
        for dx, dy in [(0, 0), (1, 0), (-1, 0), (0, 1), (0, -1)]:
            nx = x + dx
            ny = y + dy
            if 0 <= nx < SIZE and 0 <= ny < SIZE:
                self.board[ny][nx] = not self.board[ny][nx]

    def is_clear(self):
        return not any(any(row) for row in self.board)

    def update(self):
        if pyxel.btnp(pyxel.KEY_R):
            self.reset()
            return
        if self.is_clear():
            return

        if pyxel.btnp(pyxel.KEY_LEFT):
            self.cx = max(0, self.cx - 1)
        if pyxel.btnp(pyxel.KEY_RIGHT):
            self.cx = min(SIZE - 1, self.cx + 1)
        if pyxel.btnp(pyxel.KEY_UP):
            self.cy = max(0, self.cy - 1)
        if pyxel.btnp(pyxel.KEY_DOWN):
            self.cy = min(SIZE - 1, self.cy + 1)

        if pyxel.btnp(pyxel.KEY_SPACE):
            self.history.append([row[:] for row in self.board])
            self.flip(self.cx, self.cy)
            self.moves += 1
        if pyxel.btnp(pyxel.KEY_Z) and self.history:
            self.board = self.history.pop()
            self.moves -= 1

    def draw(self):
        pyxel.cls(1)
        for y in range(SIZE):
            for x in range(SIZE):
                color = 10 if self.board[y][x] else 5
                pyxel.rect(LEFT + x * CELL + 1, TOP + y * CELL + 1, CELL - 2, CELL - 2, color)
        pyxel.rectb(LEFT + self.cx * CELL, TOP + self.cy * CELL, CELL, CELL, 8)
        pyxel.text(4, 6, f"MOVES:{self.moves}", 7)
        pyxel.text(4, 108, "ARROWS+SPACE  Z:UNDO  R:RESET", 6)
        if self.is_clear():
            pyxel.text(64, 6, "CLEAR!", 10)


App()
Windows
.venv\Scripts\python.exe flip_puzzle.py
macOS / Linux
.venv/bin/python flip_puzzle.py
反転パズルのコードの役割
コードの部分役割
self.board盤面です。True が明るいマスで、self.board[y][x] のように「行・列」の順で読みます。
flip()1手の中身です。盤の外(0〜4の範囲の外)になるマスは、調べてとばします。
is_clear()すべてのマスが False なら、クリアです。
self.historyUndo のための、過去の盤面です。Space を押す前の盤面をコピーして積み、Z で取り出します。
reset()すべて消えた盤面から、START_MOVES の5か所を反転して問題を作ります。
盤面の作り方と読み方
# 5×5の盤面を作る(最初はすべて False = 消えた状態)
self.board = [[False] * SIZE for _ in range(SIZE)]

# y行目・x列目のマスを読む
self.board[y][x]
解けない問題を作らないコツ

最初の盤面を「すべて消えた状態から、何手か反転して作る」のがポイントです。同じマスを2回押すと元に戻るので、同じ5か所を押せば、必ずクリアできます。ランダムに明るいマスを置くと、解けない問題ができてしまうことがあります。

Undo の保存はコピーで

self.history.append(self.board) と書くと、今の盤面と同じリストを保存するだけです。あとで盤面を変えると、保存したはずの過去まで変わってしまいます。行ごとにコピーしてから保存しましょう。

Undo のための保存
# × 今の盤面と同じリストを指すだけ
self.history.append(self.board)

# ○ 行ごとにコピーしてから保存する
self.history.append([row[:] for row in self.board])

リストのコピーについては、共通入門のPythonの状態を読むで、短い例を動かして確かめられます。

考えてみよう

START_MOVES に (0, 0) を1つ足すと、問題が変わります。どこを押せば解けるか、予想してから遊んでみましょう。ヒント:同じマスを2回押すと、元に戻ります。

パズルを作るときの5つのコツ

このサイトのパズル講座で、くり返し出てくる考え方です。自分のパズルを作るときにも、そのまま使えます。

  1. マス目の位置とピクセルを分ける

    盤面はマスの番号(何列目・何行目)で持ち、描くときだけピクセルに変えます。マスの大きさを変えても、ルールは変わりません。(→ 画面とマップの座標、逆向き倉庫)

  2. 動けた1手だけを数える

    壁に当たって動けなかった手は、手数も時間も進めません。プレイヤーが「1手むだにした」と感じずにすみます。(→ 後から来る壁)

  3. まとめて動かすときは、先に全部調べる

    2つ以上の物が同時に動くときは、全部が動けるかを確かめてから、一度に動かします。1つずつ動かすと、順番で結果が変わってしまいます。(→ 逆向き倉庫)

  4. Undo は「戻したい値」をまとめて保存する

    位置だけでなく、手数や持ち物など、戻したい値をひとまとめにして保存します。(→ 一歩貸してください、Pythonの状態を読む)

  5. 次に起きることを見せる

    「次はこうなる」という予告を表示すると、プレイヤーが考えやすくなります。予告と実際の処理に同じ関数を使うと、2つがずれません。(→ まばたき劇場)

パズル講座32本(難易度別)

このサイトには、パズルジャンルの講座が32本あります。どの講座にも、ブラウザーで遊べる完成版、それだけで動く5段階のPythonファイル、元の完成版のソースが付いています。難易度は、コードの量や考え方の難しさから決めた目安です。

入門(2本)

コードの量が少なく、最初の1本に選びやすい講座です。

初級(15本)

マス目の移動・1手の確定・Undo など、パズルの基本がそろっています。

中級(15本)

同時に変化する仕組みや、推理・最適化の考え方まで広がります。

すべての講座を、タイトル・ジャンル・学ぶ内容から探す

どれから始める?

迷ったら、作りたいパズルに近いものから選びましょう。

目的別のおすすめパズル講座
作りたいものおすすめの講座
はじめてのマス目パズル一歩貸してください(1手前の命令を覚えて、Undo まで作る)
箱を押して動かすパズル逆向き倉庫(2つの箱がどちらも動けるときだけ動かす)
手がかりから答えを絞りこむパズル音のないピアノ、伝言は三文字
たくさんのマスが同時に変わるパズルひと箱の天気、消防水路
配置や組み合わせを考えるパズルちいさな錬金冷蔵庫(入門の目安)

Pyxelで作るのがはじめての方は、先にはじめる準備で最初の画面を出しておきましょう。講座の流れは学習順と教材の選び方にまとめています。

この記事で確認したこと

講座の一覧(タイトル・遊び方・難易度・学ぶこと)は、このサイトのトップページの講座データから、ジャンルが「パズル」の32講座を取り出して作っています。

flip_puzzle.py は、Pyxelの代わりに入力と描画命令を記録する模擬モジュールで動かし、最初の盤面がクリア状態でないこと、START_MOVES と同じ5か所を押すとクリアになること、Z で1手前の盤面に戻ること、クリア後は手が増えないこと、R で最初の盤面に戻ること、描く四角と文字が画面に収まることを確かめています。実際のPyxelのウィンドウでの表示確認は、この記事の作成時には行っていません。