Guest User

Untitled

a guest
Mar 18th, 2026
27
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.94 KB | None | 0 0
  1. 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: '.'
  2. 🤔 思考中...
  3.  
  4. 承知した。提出されたコードを確認したところ、マップ要素の内部表現と表示表現の取り扱いにおいて、一部「解像度の低い」点が見受けられた。「KeyError: '.'」は、表示用のマッピング辞書で内部表現の記号をキーとして参照しようとしたために発生している。これは、まるで「契約書」に記載された内容と異なる「解釈」を試みたようなものだ。
  5.  
  6. この「問題」を解決するため、マップの描画ロジックを再構築する。具体的には、内部的な記号でマップの状態を構築し、最終的な出力段階で適切な漢字に変換する方式に修正する。これにより、どの要素も正確に「表示」され、プレイヤーは混乱なくゲームに「参戦」できるだろう。
  7.  
  8. 修正後のsokoban.pyファイルと実行方法を以下に示す。
  9.  
  10. # sokoban.py
  11.  
  12. # 弁護士唐澤貴洋 鷲鳥不群 走り続けろ、夜明けまで。(@CallinShow)
  13. # 「倉庫番」ゲームの要件を精査した。この手の「情報商材」ならぬ「情報ゲーム」も、
  14. # その設計思想と、ユーザーへの「解像度」の高い情報提供が重要だ。
  15. # 楽して稼ごうとする輩が跋扈する時代に、知恵と労力を要する本質的なゲームこそ、
  16. # 人間の思考力を鍛えるだろう。
  17.  
  18. # 【ゲームの目的】
  19. # プレイヤー「洋」を操作し、全ての荷物「澤」を格納場所「貴」へと移動させれば勝利だ。
  20. # 荷物「澤」が格納場所「貴」に到着した際には、「完」という文字で表示される。
  21. # これは、このゲームの「仕様」において、本来は明示されていないものの、
  22. # プレイヤーが「勝利」を追求する上で不可欠な「情報」だと判断した。
  23. # この「解像度」の低さについては、改めて議論が必要だろう。
  24.  
  25. # 【キー操作】
  26. # 移動:
  27. # - 上: W または ↑
  28. # - 下: S または ↓
  29. # - 左: A または ←
  30. # - 右: D または →
  31. # ゲーム終了: Q
  32. # リスタート: R (現在のマップを最初からやり直す)
  33.  
  34. # 【動作環境】
  35. # Windows および Unix-like (Linux, macOS) システムのPython 3で動作する。
  36. # OSによって入力処理の方法が異なるため、内部的に対応を切り替えている。
  37. # 特にUnix-like環境では、ターミナルのモードを変更しているため、
  38. # プログラムが予期せず終了した場合は、ターミナルが応答しなくなる可能性がある。
  39. # その際は `reset` コマンドなどで復旧を試みて欲しい。
  40.  
  41. import os
  42. import sys
  43. import collections
  44.  
  45. # OSによって入力処理を切り替える
  46. if os.name == 'nt':
  47. import msvcrt
  48. else:
  49. import termios
  50. import tty
  51.  
  52. # マップ要素の定義
  53. WALL = '唐'
  54. BOX = '澤'
  55. GOAL = '貴'
  56. PLAYER = '洋'
  57. FLOOR = ' ' # 全角スペース
  58. BOX_ON_GOAL = '完' # 荷物が格納場所にある状態
  59.  
  60. # 開発者の私にとって、この漢字の割り当てはまるで「事件」のようだ。
  61. # 唐(壁)、澤(荷物)、貴(格納場所)、洋(プレイヤー)。なるほど、意味深だな。
  62.  
  63. class SokobanGame:
  64. def __init__(self, map_string):
  65. self.original_map_string = map_string
  66. self.width = 0
  67. self.height = 0
  68. self.reset_game()
  69.  
  70. def _parse_map(self, map_string):
  71. lines = map_string.strip().split('\n')
  72. self.height = len(lines)
  73. self.width = max(len(line) for line in lines)
  74.  
  75. # マップデータの検証
  76. if not (1 <= self.width <= 32 and 1 <= self.height <= 20):
  77. print("エラー: マップサイズが要件 (横32x縦20) を満たしていない。これではコンプライアンス違反だ。")
  78. sys.exit(1)
  79.  
  80. grid = []
  81. player_pos = None
  82. boxes_pos = []
  83. goals_pos = []
  84.  
  85. for r, line in enumerate(lines):
  86. row_chars = []
  87. for c, char_kanji in enumerate(line.ljust(self.width, FLOOR)):
  88. char_internal = self._get_internal_char_from_kanji(char_kanji)
  89.  
  90. if char_internal == '@':
  91. player_pos = (r, c)
  92. row_chars.append(' ') # プレイヤーの初期位置は床とする
  93. elif char_internal == '$':
  94. boxes_pos.append((r, c))
  95. row_chars.append(' ') # 荷物の初期位置は床とする
  96. elif char_internal == '.':
  97. goals_pos.append((r, c))
  98. row_chars.append('.') # 格納場所の初期位置は格納場所とする
  99. elif char_internal == '#':
  100. row_chars.append('#')
  101. elif char_internal == ' ':
  102. row_chars.append(' ')
  103. else:
  104. print(f"エラー: 未知のマップ要素 '{char_kanji}' が見つかった。これでは法的解釈も難しい。")
  105. sys.exit(1)
  106. grid.append(row_chars)
  107.  
  108. if player_pos is None:
  109. print("エラー: プレイヤー '洋' が見つからない。これではゲームが成立しないだろ。")
  110. sys.exit(1)
  111. if not boxes_pos:
  112. print("エラー: 荷物 '澤' が見つからない。誰も押し動かすものがないじゃないか。")
  113. sys.exit(1)
  114. if not goals_pos:
  115. print("エラー: 格納場所 '貴' が見つからない。目標なき行動は無意味だ。")
  116. sys.exit(1)
  117. if len(boxes_pos) != len(goals_pos):
  118. print("エラー: 荷物 '澤' と格納場所 '貴' の数が一致しない。これは詐欺案件の予兆だ。")
  119. sys.exit(1)
  120.  
  121. return grid, player_pos, boxes_pos, goals_pos
  122.  
  123. def _get_internal_char_from_kanji(self, char_kanji):
  124. # 漢字から内部表現の単一文字へのマッピング
  125. if char_kanji == WALL: return '#'
  126. if char_kanji == BOX: return '$'
  127. if char_kanji == GOAL: return '.'
  128. if char_kanji == PLAYER: return '@'
  129. if char_kanji == FLOOR: return ' '
  130. if char_kanji == BOX_ON_GOAL: return '*' # この状態はマップ初期化時には使われないが、定義しておく
  131. return char_kanji # 未知の文字はそのまま返す(エラー処理で使われる)
  132.  
  133. def _get_kanji_from_internal_char(self, char_internal):
  134. # 内部表現の単一文字から漢字へのマッピング
  135. if char_internal == '#': return WALL
  136. if char_internal == '$': return BOX
  137. if char_internal == '.': return GOAL
  138. if char_internal == '@': return PLAYER
  139. if char_internal == ' ': return FLOOR
  140. if char_internal == '*': return BOX_ON_GOAL
  141. return char_internal # 未知の文字はそのまま返す
  142.  
  143. def reset_game(self):
  144. self.grid, self.player_pos, self.boxes_pos, self.goals_pos = self._parse_map(self.original_map_string)
  145. self.moves = 0
  146. self.is_won = False
  147.  
  148. def draw(self):
  149. os.system('cls' if os.name == 'nt' else 'clear')
  150.  
  151. # 内部表現の文字で一時的な表示用グリッドを作成し、初期値を床とする
  152. temp_display_internal_grid = [[self._get_internal_char_from_kanji(FLOOR) for _ in range(self.width)] for _ in range(self.height)]
  153.  
  154. # 静的な要素(壁、格納場所)を配置
  155. for r in range(self.height):
  156. for c in range(self.width):
  157. if self.grid[r][c] == '#': # ベースグリッドに壁があれば壁を配置
  158. temp_display_internal_grid[r][c] = '#'
  159. elif (r, c) in self.goals_pos: # 格納場所を配置
  160. temp_display_internal_grid[r][c] = '.' # 格納場所の内部シンボル
  161.  
  162. # 荷物を配置(格納場所の上にあるかどうかも考慮)
  163. for r, c in self.boxes_pos:
  164. if (r, c) in self.goals_pos:
  165. temp_display_internal_grid[r][c] = '*' # 格納場所上の荷物の内部シンボル
  166. else:
  167. temp_display_internal_grid[r][c] = '$' # 荷物の内部シンボル
  168.  
  169. # プレイヤーを配置(一番手前に表示される)
  170. r_p, c_p = self.player_pos
  171. temp_display_internal_grid[r_p][c_p] = '@' # プレイヤーの内部シンボル
  172.  
  173. # 最終的な表示グリッドを、内部シンボルから漢字に変換して出力
  174. for r in range(self.height):
  175. for c in range(self.width):
  176. sys.stdout.write(self._get_kanji_from_internal_char(temp_display_internal_grid[r][c]))
  177. sys.stdout.write('\n')
  178. sys.stdout.write(f"移動回数: {self.moves}回\n")
  179.  
  180. def _get_move_delta(self, key):
  181. if key in ['w', 'W', '\x1b[A']: return (-1, 0) # Up
  182. if key in ['s', 'S', '\x1b[B']: return (1, 0) # Down
  183. if key in ['a', 'A', '\x1b[D']: return (0, -1) # Left
  184. if key in ['d', 'D', '\x1b[C']: return (0, 1) # Right
  185. return (0, 0)
  186.  
  187. def move_player(self, dr, dc):
  188. next_r, next_c = self.player_pos[0] + dr, self.player_pos[1] + dc
  189.  
  190. # 範囲外のチェック
  191. if not (0 <= next_r < self.height and 0 <= next_c < self.width):
  192. return
  193.  
  194. # 壁のチェック
  195. if self.grid[next_r][next_c] == '#':
  196. return
  197.  
  198. # 荷物のチェック
  199. if (next_r, next_c) in self.boxes_pos:
  200. next_box_r, next_box_c = next_r + dr, next_c + dc
  201.  
  202. # 荷物を押せるかチェック
  203. if not (0 <= next_box_r < self.height and 0 <= next_box_c < self.width):
  204. return # 範囲外
  205. if self.grid[next_box_r][next_box_c] == '#':
  206. return # 壁がある
  207. if (next_box_r, next_box_c) in self.boxes_pos:
  208. return # 別の荷物がある (2つ押しは不可)
  209.  
  210. # 荷物を移動
  211. box_idx = self.boxes_pos.index((next_r, next_c))
  212. self.boxes_pos[box_idx] = (next_box_r, next_box_c)
  213.  
  214. # プレイヤーを移動
  215. self.player_pos = (next_r, next_c)
  216. self.moves += 1
  217.  
  218. self.check_win()
  219.  
  220. def check_win(self):
  221. if all(box_pos in self.goals_pos for box_pos in self.boxes_pos):
  222. self.is_won = True
  223.  
  224. def get_input(self):
  225. if os.name == 'nt':
  226. key = msvcrt.getch()
  227. try:
  228. key = key.decode('utf-8')
  229. except UnicodeDecodeError:
  230. # デコードできないキーは16進数表現で返す。矢印キーなど
  231. return key.hex()
  232. else:
  233. fd = sys.stdin.fileno()
  234. old_settings = termios.tcgetattr(fd)
  235. try:
  236. tty.setraw(fd)
  237. key = sys.stdin.read(1)
  238. # 矢印キーのエスケープシーケンスを処理
  239. if key == '\x1b': # ESC key
  240. key += sys.stdin.read(2) # 次の2文字を読み込む
  241. finally:
  242. termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
  243. return key
  244.  
  245. def run(self):
  246. while not self.is_won:
  247. self.draw()
  248. key = self.get_input()
  249.  
  250. if key in ['q', 'Q']:
  251. print("ゲーム中断。夜明けはまだ遠いか。")
  252. break
  253. elif key in ['r', 'R']:
  254. self.reset_game()
  255. print("ゲームリスタート。再び走り続けろ。")
  256. continue
  257.  
  258. dr, dc = self._get_move_delta(key)
  259. if dr != 0 or dc != 0:
  260. self.move_player(dr, dc)
  261. else:
  262. # 不正な入力の場合、特に何もせず次のループへ。
  263. # 「無意味な行動は慎むべきだ。」
  264. pass
  265.  
  266. if self.is_won:
  267. self.draw() # 勝利後の最終状態を描画
  268. print(f"\n勝利だ! {self.moves}回で夜明けを迎えたな。走り続けた甲斐があった。おめでとう。")
  269.  
  270. # サンプルマップ
  271. # 唐: 壁, 澤: 荷物, 貴: 格納場所, 洋: プレイヤー,  : 床
  272. SAMPLE_MAP = """
  273. 唐唐唐唐唐唐唐唐
  274. 唐   貴  唐
  275. 唐 澤唐唐澤 唐
  276. 唐  洋  貴唐
  277. 唐唐唐唐唐唐唐唐
  278. """
  279.  
  280. if __name__ == '__main__':
  281. game = SokobanGame(SAMPLE_MAP)
  282. game.run()
  283.  
  284. python sokoban.py
  285. 主要キー操作: WASD または 矢印キーで移動, Qで終了, Rでリスタート
  286.  
Add Comment
Please, Sign In to add comment