Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Traceback (most recent call last): File "~/sokoban.py", line 278, in <module> game.run() ~~~~~~~~^^ File "~/sokoban.py", line 243, in run self.draw() ~~~~~~~~~^^ File "~/sokoban.py", line 156, in draw display_grid[r][c] = CHAR_MAP_EXTERNAL['.'] # GOAL ~~~~~~~~~~~~~~~~~^^^^^ KeyError: '.'
- 🤔 思考中...
- 承知した。提出されたコードを確認したところ、マップ要素の内部表現と表示表現の取り扱いにおいて、一部「解像度の低い」点が見受けられた。「KeyError: '.'」は、表示用のマッピング辞書で内部表現の記号をキーとして参照しようとしたために発生している。これは、まるで「契約書」に記載された内容と異なる「解釈」を試みたようなものだ。
- この「問題」を解決するため、マップの描画ロジックを再構築する。具体的には、内部的な記号でマップの状態を構築し、最終的な出力段階で適切な漢字に変換する方式に修正する。これにより、どの要素も正確に「表示」され、プレイヤーは混乱なくゲームに「参戦」できるだろう。
- 修正後のsokoban.pyファイルと実行方法を以下に示す。
- # sokoban.py
- # 弁護士唐澤貴洋 鷲鳥不群 走り続けろ、夜明けまで。(@CallinShow)
- # 「倉庫番」ゲームの要件を精査した。この手の「情報商材」ならぬ「情報ゲーム」も、
- # その設計思想と、ユーザーへの「解像度」の高い情報提供が重要だ。
- # 楽して稼ごうとする輩が跋扈する時代に、知恵と労力を要する本質的なゲームこそ、
- # 人間の思考力を鍛えるだろう。
- # 【ゲームの目的】
- # プレイヤー「洋」を操作し、全ての荷物「澤」を格納場所「貴」へと移動させれば勝利だ。
- # 荷物「澤」が格納場所「貴」に到着した際には、「完」という文字で表示される。
- # これは、このゲームの「仕様」において、本来は明示されていないものの、
- # プレイヤーが「勝利」を追求する上で不可欠な「情報」だと判断した。
- # この「解像度」の低さについては、改めて議論が必要だろう。
- # 【キー操作】
- # 移動:
- # - 上: W または ↑
- # - 下: S または ↓
- # - 左: A または ←
- # - 右: D または →
- # ゲーム終了: Q
- # リスタート: R (現在のマップを最初からやり直す)
- # 【動作環境】
- # Windows および Unix-like (Linux, macOS) システムのPython 3で動作する。
- # OSによって入力処理の方法が異なるため、内部的に対応を切り替えている。
- # 特にUnix-like環境では、ターミナルのモードを変更しているため、
- # プログラムが予期せず終了した場合は、ターミナルが応答しなくなる可能性がある。
- # その際は `reset` コマンドなどで復旧を試みて欲しい。
- import os
- import sys
- import collections
- # OSによって入力処理を切り替える
- if os.name == 'nt':
- import msvcrt
- else:
- import termios
- import tty
- # マップ要素の定義
- WALL = '唐'
- BOX = '澤'
- GOAL = '貴'
- PLAYER = '洋'
- FLOOR = ' ' # 全角スペース
- BOX_ON_GOAL = '完' # 荷物が格納場所にある状態
- # 開発者の私にとって、この漢字の割り当てはまるで「事件」のようだ。
- # 唐(壁)、澤(荷物)、貴(格納場所)、洋(プレイヤー)。なるほど、意味深だな。
- class SokobanGame:
- def __init__(self, map_string):
- self.original_map_string = map_string
- self.width = 0
- self.height = 0
- self.reset_game()
- def _parse_map(self, map_string):
- lines = map_string.strip().split('\n')
- self.height = len(lines)
- self.width = max(len(line) for line in lines)
- # マップデータの検証
- if not (1 <= self.width <= 32 and 1 <= self.height <= 20):
- print("エラー: マップサイズが要件 (横32x縦20) を満たしていない。これではコンプライアンス違反だ。")
- sys.exit(1)
- grid = []
- player_pos = None
- boxes_pos = []
- goals_pos = []
- for r, line in enumerate(lines):
- row_chars = []
- for c, char_kanji in enumerate(line.ljust(self.width, FLOOR)):
- char_internal = self._get_internal_char_from_kanji(char_kanji)
- if char_internal == '@':
- player_pos = (r, c)
- row_chars.append(' ') # プレイヤーの初期位置は床とする
- elif char_internal == '$':
- boxes_pos.append((r, c))
- row_chars.append(' ') # 荷物の初期位置は床とする
- elif char_internal == '.':
- goals_pos.append((r, c))
- row_chars.append('.') # 格納場所の初期位置は格納場所とする
- elif char_internal == '#':
- row_chars.append('#')
- elif char_internal == ' ':
- row_chars.append(' ')
- else:
- print(f"エラー: 未知のマップ要素 '{char_kanji}' が見つかった。これでは法的解釈も難しい。")
- sys.exit(1)
- grid.append(row_chars)
- if player_pos is None:
- print("エラー: プレイヤー '洋' が見つからない。これではゲームが成立しないだろ。")
- sys.exit(1)
- if not boxes_pos:
- print("エラー: 荷物 '澤' が見つからない。誰も押し動かすものがないじゃないか。")
- sys.exit(1)
- if not goals_pos:
- print("エラー: 格納場所 '貴' が見つからない。目標なき行動は無意味だ。")
- sys.exit(1)
- if len(boxes_pos) != len(goals_pos):
- print("エラー: 荷物 '澤' と格納場所 '貴' の数が一致しない。これは詐欺案件の予兆だ。")
- sys.exit(1)
- return grid, player_pos, boxes_pos, goals_pos
- def _get_internal_char_from_kanji(self, char_kanji):
- # 漢字から内部表現の単一文字へのマッピング
- if char_kanji == WALL: return '#'
- if char_kanji == BOX: return '$'
- if char_kanji == GOAL: return '.'
- if char_kanji == PLAYER: return '@'
- if char_kanji == FLOOR: return ' '
- if char_kanji == BOX_ON_GOAL: return '*' # この状態はマップ初期化時には使われないが、定義しておく
- return char_kanji # 未知の文字はそのまま返す(エラー処理で使われる)
- def _get_kanji_from_internal_char(self, char_internal):
- # 内部表現の単一文字から漢字へのマッピング
- if char_internal == '#': return WALL
- if char_internal == '$': return BOX
- if char_internal == '.': return GOAL
- if char_internal == '@': return PLAYER
- if char_internal == ' ': return FLOOR
- if char_internal == '*': return BOX_ON_GOAL
- return char_internal # 未知の文字はそのまま返す
- def reset_game(self):
- self.grid, self.player_pos, self.boxes_pos, self.goals_pos = self._parse_map(self.original_map_string)
- self.moves = 0
- self.is_won = False
- def draw(self):
- os.system('cls' if os.name == 'nt' else 'clear')
- # 内部表現の文字で一時的な表示用グリッドを作成し、初期値を床とする
- temp_display_internal_grid = [[self._get_internal_char_from_kanji(FLOOR) for _ in range(self.width)] for _ in range(self.height)]
- # 静的な要素(壁、格納場所)を配置
- for r in range(self.height):
- for c in range(self.width):
- if self.grid[r][c] == '#': # ベースグリッドに壁があれば壁を配置
- temp_display_internal_grid[r][c] = '#'
- elif (r, c) in self.goals_pos: # 格納場所を配置
- temp_display_internal_grid[r][c] = '.' # 格納場所の内部シンボル
- # 荷物を配置(格納場所の上にあるかどうかも考慮)
- for r, c in self.boxes_pos:
- if (r, c) in self.goals_pos:
- temp_display_internal_grid[r][c] = '*' # 格納場所上の荷物の内部シンボル
- else:
- temp_display_internal_grid[r][c] = '$' # 荷物の内部シンボル
- # プレイヤーを配置(一番手前に表示される)
- r_p, c_p = self.player_pos
- temp_display_internal_grid[r_p][c_p] = '@' # プレイヤーの内部シンボル
- # 最終的な表示グリッドを、内部シンボルから漢字に変換して出力
- for r in range(self.height):
- for c in range(self.width):
- sys.stdout.write(self._get_kanji_from_internal_char(temp_display_internal_grid[r][c]))
- sys.stdout.write('\n')
- sys.stdout.write(f"移動回数: {self.moves}回\n")
- def _get_move_delta(self, key):
- if key in ['w', 'W', '\x1b[A']: return (-1, 0) # Up
- if key in ['s', 'S', '\x1b[B']: return (1, 0) # Down
- if key in ['a', 'A', '\x1b[D']: return (0, -1) # Left
- if key in ['d', 'D', '\x1b[C']: return (0, 1) # Right
- return (0, 0)
- def move_player(self, dr, dc):
- next_r, next_c = self.player_pos[0] + dr, self.player_pos[1] + dc
- # 範囲外のチェック
- if not (0 <= next_r < self.height and 0 <= next_c < self.width):
- return
- # 壁のチェック
- if self.grid[next_r][next_c] == '#':
- return
- # 荷物のチェック
- if (next_r, next_c) in self.boxes_pos:
- next_box_r, next_box_c = next_r + dr, next_c + dc
- # 荷物を押せるかチェック
- if not (0 <= next_box_r < self.height and 0 <= next_box_c < self.width):
- return # 範囲外
- if self.grid[next_box_r][next_box_c] == '#':
- return # 壁がある
- if (next_box_r, next_box_c) in self.boxes_pos:
- return # 別の荷物がある (2つ押しは不可)
- # 荷物を移動
- box_idx = self.boxes_pos.index((next_r, next_c))
- self.boxes_pos[box_idx] = (next_box_r, next_box_c)
- # プレイヤーを移動
- self.player_pos = (next_r, next_c)
- self.moves += 1
- self.check_win()
- def check_win(self):
- if all(box_pos in self.goals_pos for box_pos in self.boxes_pos):
- self.is_won = True
- def get_input(self):
- if os.name == 'nt':
- key = msvcrt.getch()
- try:
- key = key.decode('utf-8')
- except UnicodeDecodeError:
- # デコードできないキーは16進数表現で返す。矢印キーなど
- return key.hex()
- else:
- fd = sys.stdin.fileno()
- old_settings = termios.tcgetattr(fd)
- try:
- tty.setraw(fd)
- key = sys.stdin.read(1)
- # 矢印キーのエスケープシーケンスを処理
- if key == '\x1b': # ESC key
- key += sys.stdin.read(2) # 次の2文字を読み込む
- finally:
- termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
- return key
- def run(self):
- while not self.is_won:
- self.draw()
- key = self.get_input()
- if key in ['q', 'Q']:
- print("ゲーム中断。夜明けはまだ遠いか。")
- break
- elif key in ['r', 'R']:
- self.reset_game()
- print("ゲームリスタート。再び走り続けろ。")
- continue
- dr, dc = self._get_move_delta(key)
- if dr != 0 or dc != 0:
- self.move_player(dr, dc)
- else:
- # 不正な入力の場合、特に何もせず次のループへ。
- # 「無意味な行動は慎むべきだ。」
- pass
- if self.is_won:
- self.draw() # 勝利後の最終状態を描画
- print(f"\n勝利だ! {self.moves}回で夜明けを迎えたな。走り続けた甲斐があった。おめでとう。")
- # サンプルマップ
- # 唐: 壁, 澤: 荷物, 貴: 格納場所, 洋: プレイヤー, : 床
- SAMPLE_MAP = """
- 唐唐唐唐唐唐唐唐
- 唐 貴 唐
- 唐 澤唐唐澤 唐
- 唐 洋 貴唐
- 唐唐唐唐唐唐唐唐
- """
- if __name__ == '__main__':
- game = SokobanGame(SAMPLE_MAP)
- game.run()
- python sokoban.py
- 主要キー操作: WASD または 矢印キーで移動, Qで終了, Rでリスタート
Add Comment
Please, Sign In to add comment