BEFORE THE GAME CODE
Pythonの「状態」を読む。
講座のコードに class・self・リスト・dataclass が出てきて、手が止まってしまったら。5つの短い例で、ゲームの値が「どこに覚えられて」「どう変わるか」を確かめます。
- 前提
- 変数・if・関数を少し使ったことがある
- 環境
- Python 3.11以上。追加ライブラリー・Pyxel不要
- 作るもの
- ターミナルに結果を出す五つの小さなプログラム
100本のゲーム講座とは別の、共通の入門ページです。Pythonのすべてを説明するものではなく、ゲーム講座のコードを読むときに必要な「データの持ち方」にしぼっています。
ZIPをすべて展開し、README.txt があるフォルダーでターミナルを開きます。実行する前に「何が表示されるか」を予想してから動かしてみましょう。そのあと1か所だけ変えて、結果を比べます。
関数の返り値を、次の状態として受け取る
値を計算することと、変数へ戻すこと
ゲームの「状態」とは、「いまどこにいるか」「何を持っているか」のように、その時点で覚えておく値のことです。ここでは x(横の位置)だけを使い、今の20から右へ4進んだ24を計算します。
move_right(x, 4) は24を返します。でも、関数を呼んだだけでは、外側の x は20のままです。x = next_x と書いて受け取ったときに、はじめて次の状態になります。ゲームの update を読むときも、「今の値を読む → 次の値を計算する → 保存する」の順番を探すと、流れを追いやすくなります。
next_x = move_right(x, 4)
print("before:", x)
print("returned:", next_x)
x = next_xそのまま動かす
ZIPを展開したフォルダーで実行します。Windows は py -3 01_values.py、macOS / Linux は python3 01_values.py です。結果は、ゲーム画面ではなくターミナルに文字で表示されます。
before: 20
returned: 24
after: 24「関数に渡した値を変えれば、外側の変数も必ず変わる」と考えないようにしましょう。この例の整数は、関数が新しい値を返し、それを自分で代入しています。
呼び出しの4を8に変えると、返ってくる値と最後の x は28、最初の x は20です。実行する前に、3行の結果を予想してみましょう。
単独で動く全文を見る
# MIT License
#
# Copyright (c) 2026 Tidepost contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Exception: assets/glyphs.json contains rasterized glyph data derived from
# Noto Sans CJK JP (SIL Open Font License 1.1). See assets/NOTO-LICENSE.txt.
# Pyxel is an external dependency under its own MIT license.
"""変数の値と、関数が返す次の値。"""
def move_right(x, distance):
return x + distance
def main():
x = 20
next_x = move_right(x, 4)
print("before:", x)
print("returned:", next_x)
x = next_x
print("after:", x)
if __name__ == "__main__":
main()self.xは、どのプレイヤーの値?
オブジェクトに残る属性と、その場の変数
class Player は、プレイヤーを作るための「設計図」です。Player() を2回呼ぶと、first と second という別々のプレイヤーができます。__init__ は作るときに自動で呼ばれる処理で、self.x = 20 で、そのプレイヤーの最初の位置を覚えます。
self は「いま呼ばれている、そのプレイヤー自身」のことです。first.move_right(4) の中の self.x は first.x のことで、second.x には触りません。クラスの中に書いた関数を「メソッド」と呼び、呼び出したときに、そのプレイヤーが self として渡されます。
temporary_value の中の x = 100 は、その関数の中だけで使う変数です。self.x ではないので、プレイヤーの位置は変わりません。名前が似ていても、覚えておく場所が違います。各講座の App や Game にも、self. を付けてそのオブジェクトの値を読むところがあります。
def move_right(self, distance):
self.x += distance
def temporary_value(self):
x = 100
return xそのまま動かす
ZIPを展開したフォルダーで実行します。Windows は py -3 02_attributes.py、macOS / Linux は python3 02_attributes.py です。結果はターミナルに表示されます。
first: 24
second: 20
temporary: 100
still first: 24x と self.x を取りちがえると、画面の位置が変わらなかったり、思っていない場所に値が保存されたりします。「この値はどこに残すのか」を意識して読みましょう。
first.move_right(4) の次の行に second.move_right(8) を追加します。first は24、second は28になり、2人の状態が別々に覚えられていることを確かめられます。
単独で動く全文を見る
# MIT License
#
# Copyright (c) 2026 Tidepost contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Exception: assets/glyphs.json contains rasterized glyph data derived from
# Noto Sans CJK JP (SIL Open Font License 1.1). See assets/NOTO-LICENSE.txt.
# Pyxel is an external dependency under its own MIT license.
"""self.xは、このPlayerに残る値。"""
class Player:
def __init__(self):
self.x = 20
def move_right(self, distance):
self.x += distance
def temporary_value(self):
x = 100
return x
def main():
first = Player()
second = Player()
first.move_right(4)
print("first:", first.x)
print("second:", second.x)
print("temporary:", first.temporary_value())
print("still first:", first.x)
if __name__ == "__main__":
main()リスト・タプル・辞書で複数の値を持つ
別名を付けることと、コピーすることは違う
リスト bag は、持ち物を順番に並べた入れ物です。append すると、同じリストの後ろに追加されます。alias = bag は新しい袋を作るのではなく、同じリストに別の名前を付けただけです。そのため、alias から鍵を足すと、bag にも鍵が入ります。
bag.copy() は、新しいリストを作ります。この例では中身が文字なので、元のリストに追加しても、コピーのほうは変わりません。ただし、リストの中にリストが入っているような場合は、中身まで自動で別々にはなりません(深いコピーではありません)。
タプル (2, 3) は「列と行」の組です。position[0] が列、[1] が行です。中身を直接書きかえず、次の座標 (3, 3) を新しく作っています。辞書は x や hp のような名前(キー)で値を取り出せる入れ物で、player["hp"] で体力を読めます。
bag = ["sheet"]
alias = bag
copy = bag.copy()
alias.append("key")そのまま動かす
ZIPを展開したフォルダーで実行します。Windows は py -3 03_collections.py、macOS / Linux は python3 03_collections.py です。結果はターミナルに表示されます。
bag: ['sheet', 'key']
copy: ['sheet']
positions: (2, 3) (3, 3)
named values: 20 2Undo(1つ前に戻す)のために old = bag と書いても、同じリストを指すだけです。今の袋を書きかえると、保存したつもりの「過去」まで変わってしまいます。
alias = bag を alias = bag.copy() に変えてみましょう。alias に鍵を追加しても、bag は ['sheet'] のままです。必要なら print(alias) も追加して比べます。
単独で動く全文を見る
# MIT License
#
# Copyright (c) 2026 Tidepost contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Exception: assets/glyphs.json contains rasterized glyph data derived from
# Noto Sans CJK JP (SIL Open Font License 1.1). See assets/NOTO-LICENSE.txt.
# Pyxel is an external dependency under its own MIT license.
"""同じリストへの別名と、独立した入れ物。"""
def main():
bag = ["sheet"]
alias = bag
copy = bag.copy()
alias.append("key")
print("bag:", bag)
print("copy:", copy)
position = (2, 3)
next_position = (position[0] + 1, position[1])
print("positions:", position, next_position)
player = {"x": 20, "hp": 3}
player["hp"] -= 1
print("named values:", player["x"], player["hp"])
if __name__ == "__main__":
main()dataclassとreplaceで過去を残す
次の状態を作り、前の状態を保持
@dataclass は、いくつかの値をまとめて持つ「箱」を、短く書けるようにする仕組みです。x: int = 20 は位置、bag: tuple = () は空の袋です。: int のような型の書きこみは読む人への案内で、型がちがう値を実行中に自動で止めてくれるわけではありません。
frozen=True を付けると、作ったあとで state.x = 99 のように値を書きかえられなくなります。値を変えたいときは replace を使い、変えたいところだけを指定して、新しい State を作ります。bag もタプルにして、新しい持ち物の組を作ります。
history に before を保存したあとで current を作り直しても、before は変わりません。pop で最後に保存した状態を取り出せば、それが Undo になります。なお frozen は、中に入ったリストまでは固定しません。そのため、この例では中身も整数とタプルにしています。
def collect(state):
return replace(state, x=state.x + 4, bag=state.bag + ("sheet",))そのまま動かす
ZIPを展開したフォルダーで実行します。Windows は py -3 04_snapshots.py、macOS / Linux は python3 04_snapshots.py です。結果はターミナルに表示されます。
before: 20 ()
current: 24 ('sheet',)
undo: 20 ()「今の値を少しずつ書きかえるやり方」と、「書きかえられない State を作り直すやり方」を、途中で混ぜないようにしましょう。講座ごとに、どちらを使っているかを確かめてから読みます。
collect を2回行い、そのたびに直前の State を history に追加します。1回 pop すると、x が24・シーツ1枚の状態に戻るはずです。最初の State だけを保存した場合は、いっきに x 20まで戻ります。この違いも比べてみましょう。
単独で動く全文を見る
# MIT License
#
# Copyright (c) 2026 Tidepost contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Exception: assets/glyphs.json contains rasterized glyph data derived from
# Noto Sans CJK JP (SIL Open Font License 1.1). See assets/NOTO-LICENSE.txt.
# Pyxel is an external dependency under its own MIT license.
"""不変の値を作り直し、前の状態を残す。"""
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class State:
x: int = 20
bag: tuple = ()
def collect(state):
return replace(state, x=state.x + 4, bag=state.bag + ("sheet",))
def main():
before = State()
history = [before]
current = collect(before)
print("before:", before.x, before.bag)
print("current:", current.x, current.bag)
current = history.pop()
print("undo:", current.x, current.bag)
if __name__ == "__main__":
main()更新する処理と、表示する処理を分ける
何度描画しても状態は進まない
最後の例ではPyxelを使わず、画面の代わりに文字を作って確かめます。update は RIGHT という入力を受け取ったときだけ、新しい位置を返します。draw_text は状態を読むだけで、X=20 のような文字を作ります。
同じ状態を2回表示しても、位置は20のままです。そのあと RIGHT を1回受け取って、はじめて24になります。ゲームの draw もこのように「読むだけ」にしておくと、画面を描いた回数でゲームが勝手に進むまちがいを防げます。
ここでの RIGHT は説明用の文字で、本物のキーボードは読んでいません。Pyxel では、btn や btnp で受け取った入力を update に渡し、draw で表示します。画面のある講座を読むときも、「入力」「状態を変える」「描く」の3か所を分けて探してみましょう。
def update(state, key):
return replace(state, x=state.x + 4) if key == "RIGHT" else state
def draw_text(state):
return "X=" + str(state.x)そのまま動かす
ZIPを展開したフォルダーで実行します。Windows は py -3 05_update_display.py、macOS / Linux は python3 05_update_display.py です。結果はターミナルに表示されます。
X=20
X=20
X=24表示する関数の中で x を増やすと、画面を描き直した回数だけキャラクターが動いてしまいます。「見せる処理」と「値を決める処理」は分けましょう。
state = update(state, "RIGHT") をもう1行追加してから表示すると、X=28 になります。print だけを追加しても数字は増えないことも確かめましょう。
単独で動く全文を見る
# MIT License
#
# Copyright (c) 2026 Tidepost contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Exception: assets/glyphs.json contains rasterized glyph data derived from
# Noto Sans CJK JP (SIL Open Font License 1.1). See assets/NOTO-LICENSE.txt.
# Pyxel is an external dependency under its own MIT license.
"""入力で変える処理と、読むだけの表示を分ける。"""
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class State:
x: int = 20
def update(state, key):
return replace(state, x=state.x + 4) if key == "RIGHT" else state
def draw_text(state):
return "X=" + str(state.x)
def main():
state = State()
print(draw_text(state))
print(draw_text(state))
state = update(state, "RIGHT")
print(draw_text(state))
if __name__ == "__main__":
main()次は、画面と入力へ。
入力・更新・描画を分けるでは、キーを押し続けたときと1回押したときの違いや、止められるゲームの時計を、実際のPyxelの画面で比べます。
ゲームのコードへ戻る。
値がどこに覚えられているかが分かったら、おばけ洗濯日和で位置と得点を探したり、一歩貸してくださいで「過去の状態」と Undo を読んだりしてみましょう。オブジェクトを書きかえるやり方か、State を作り直すやり方かは、それぞれの講座に書いてあります。
確認したこと
Linux / Python 3.12.14で28テストと、各ファイルを日本語・空白の別フォルダーへ移した単独実行を確認。表示結果・改造例・ソースの抜粋を照合しました。CP932既定の読み取りも模擬検査しています。
Windows / macOSの実機操作、初学者による試読・理解度の確認は未実施です。このページの例はキーボード操作のゲームではなく、Pyxelやブラウザー内ゲームの動作検査でもありません。
コードには教材のMIT通知を保持しています。参照先の権利文書もZIPに含めますが、この入門例には外部フォントや字形データは使っていません。