Guest User

Untitled

a guest
Mar 18th, 2026
50
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.34 KB | None | 0 0
  1. 端末上で動作するテキストベースの倉庫番(Sokoban)ゲームを単一ファイルで実装してください。要件は次の通りです:フィールド最大横32×縦20、要素は壁(唐)、荷物(澤)、格納場所(貴)、プレイヤー(洋)、床は空白。プレイヤーは上下左右(矢印キーまたは WASD)で移動し、進行方向に荷物がある場合は荷物を押して移動できる(同時に押せる荷物は1つのみ、荷物を引く操作は実装しない)。各ターンで端末にマップを再描画し、移動回数を表示。すべての荷物が格納場所にあれば勝利メッセージを表示して終了する。マップ読み込み・検証・描画・入力処理・ゲームループ・勝利判定・衝突判定を含め、ファイル冒頭に使い方とキー操作をコメントで記載し、簡単なサンプルマップを1つ含めてください。外部ライブラリは使わず、標準ライブラリのみで実装してください。出力は sokoban.py というファイルに保存できる完全なコードとして返してください。コードの後に3行以内で実行方法と主要キー操作を記載してください。
  2. 🤔 思考中...
  3.  
  4. # sokoban.py
  5.  
  6. # 弁護士唐澤貴洋 鷲鳥不群 走り続けろ、夜明けまで。(@CallinShow)
  7. # 「倉庫番」ゲームの要件を精査した。この手の「情報商材」ならぬ「情報ゲーム」も、
  8. # その設計思想と、ユーザーへの「解像度」の高い情報提供が重要だ。
  9. # 楽して稼ごうとする輩が跋扈する時代に、知恵と労力を要する本質的なゲームこそ、
  10. # 人間の思考力を鍛えるだろう。
  11.  
  12. # 【ゲームの目的】
  13. # プレイヤー「洋」を操作し、全ての荷物「澤」を格納場所「貴」へと移動させれば勝利だ。
  14. # 荷物「澤」が格納場所「貴」に到着した際には、「完」という文字で表示される。
  15. # これは、このゲームの「仕様」において、本来は明示されていないものの、
  16. # プレイヤーが「勝利」を追求する上で不可欠な「情報」だと判断した。
  17. # この「解像度」の低さについては、改めて議論が必要だろう。
  18.  
  19. # 【キー操作】
  20. # 移動:
  21. # - 上: W または ↑
  22. # - 下: S または ↓
  23. # - 左: A または ←
  24. # - 右: D または →
  25. # ゲーム終了: Q
  26. # リスタート: R (現在のマップを最初からやり直す)
  27.  
  28. # 【動作環境】
  29. # Windows および Unix-like (Linux, macOS) システムのPython 3で動作する。
  30. # OSによって入力処理の方法が異なるため、内部的に対応を切り替えている。
  31. # 特にUnix-like環境では、ターミナルのモードを変更しているため、
  32. # プログラムが予期せず終了した場合は、ターミナルが応答しなくなる可能性がある。
  33. # その際は `reset` コマンドなどで復旧を試みて欲しい。
  34.  
  35. import os
  36. import sys
  37. import collections
  38.  
  39. # OSによって入力処理を切り替える
  40. if os.name == 'nt':
  41. import msvcrt
  42. else:
  43. import termios
  44. import tty
  45.  
  46. # マップ要素の定義
  47. WALL = '唐'
  48. BOX = '澤'
  49. GOAL = '貴'
  50. PLAYER = '洋'
  51. FLOOR = ' ' # 全角スペース
  52. BOX_ON_GOAL = '完' # 荷物が格納場所にある状態
  53.  
  54. # 内部表現用の半角文字マッピング(処理の都合上)
  55. CHAR_MAP_INTERNAL = {
  56. '#': WALL,
  57. '$': BOX,
  58. '.': GOAL,
  59. '@': PLAYER,
  60. ' ': FLOOR,
  61. '*': BOX_ON_GOAL # Internal state for box on goal
  62. }
  63. # 逆マッピング(表示用)
  64. CHAR_MAP_EXTERNAL = {v: k for k, v in CHAR_MAP_INTERNAL.items()}
  65.  
  66. # 開発者の私にとって、この漢字の割り当てはまるで「事件」のようだ。
  67. # 唐(壁)、澤(荷物)、貴(格納場所)、洋(プレイヤー)。なるほど、意味深だな。
  68.  
  69. class SokobanGame:
  70. def __init__(self, map_string):
  71. self.original_map_string = map_string
  72. self.width = 0
  73. self.height = 0
  74. self.reset_game()
  75.  
  76. def _parse_map(self, map_string):
  77. lines = map_string.strip().split('\n')
  78. self.height = len(lines)
  79. self.width = max(len(line) for line in lines)
  80.  
  81. # マップデータの検証
  82. if not (1 <= self.width <= 32 and 1 <= self.height <= 20):
  83. print("エラー: マップサイズが要件 (横32x縦20) を満たしていない。これではコンプライアンス違反だ。")
  84. sys.exit(1)
  85.  
  86. grid = []
  87. player_pos = None
  88. boxes_pos = []
  89. goals_pos = []
  90.  
  91. for r, line in enumerate(lines):
  92. row_chars = []
  93. for c, char_kanji in enumerate(line.ljust(self.width, FLOOR)):
  94. char_internal = self._get_internal_char(char_kanji)
  95.  
  96. if char_internal == '@':
  97. player_pos = (r, c)
  98. row_chars.append(' ') # プレイヤーの初期位置は床とする
  99. elif char_internal == '$':
  100. boxes_pos.append((r, c))
  101. row_chars.append(' ') # 荷物の初期位置は床とする
  102. elif char_internal == '.':
  103. goals_pos.append((r, c))
  104. row_chars.append('.') # 格納場所の初期位置は格納場所とする
  105. elif char_internal == '#':
  106. row_chars.append('#')
  107. elif char_internal == ' ':
  108. row_chars.append(' ')
  109. else:
  110. print(f"エラー: 未知のマップ要素 '{char_kanji}' が見つかった。これでは法的解釈も難しい。")
  111. sys.exit(1)
  112. grid.append(row_chars)
  113.  
  114. if player_pos is None:
  115. print("エラー: プレイヤー '洋' が見つからない。これではゲームが成立しないだろ。")
  116. sys.exit(1)
  117. if not boxes_pos:
  118. print("エラー: 荷物 '澤' が見つからない。誰も押し動かすものがないじゃないか。")
  119. sys.exit(1)
  120. if not goals_pos:
  121. print("エラー: 格納場所 '貴' が見つからない。目標なき行動は無意味だ。")
  122. sys.exit(1)
  123. if len(boxes_pos) != len(goals_pos):
  124. print("エラー: 荷物 '澤' と格納場所 '貴' の数が一致しない。これは詐欺案件の予兆だ。")
  125. sys.exit(1)
  126.  
  127. return grid, player_pos, boxes_pos, goals_pos
  128.  
  129. def _get_internal_char(self, char_kanji):
  130. if char_kanji == WALL: return '#'
  131. if char_kanji == BOX: return '$'
  132. if char_kanji == GOAL: return '.'
  133. if char_kanji == PLAYER: return '@'
  134. if char_kanji == FLOOR: return ' '
  135. # プレイ中に動的に変わる状態
  136. if char_kanji == BOX_ON_GOAL: return '*'
  137. return char_kanji # その他の文字はそのまま返す(エラー処理で使われる)
  138.  
  139. def _get_external_char(self, char_internal):
  140. if char_internal == '#': return WALL
  141. if char_internal == '$': return BOX
  142. if char_internal == '.': return GOAL
  143. if char_internal == '@': return PLAYER
  144. if char_internal == ' ': return FLOOR
  145. if char_internal == '*': return BOX_ON_GOAL
  146. return char_internal
  147.  
  148. def reset_game(self):
  149. self.grid, self.player_pos, self.boxes_pos, self.goals_pos = self._parse_map(self.original_map_string)
  150. self.moves = 0
  151. self.is_won = False
  152.  
  153. def draw(self):
  154. os.system('cls' if os.name == 'nt' else 'clear')
  155. display_grid = [row[:] for row in self.grid] # gridのコピー
  156.  
  157. # 格納場所を表示
  158. for r, c in self.goals_pos:
  159. display_grid[r][c] = CHAR_MAP_EXTERNAL['.'] # GOAL
  160.  
  161. # 荷物を表示 (ゴール上の荷物も含む)
  162. for r, c in self.boxes_pos:
  163. if (r, c) in self.goals_pos:
  164. display_grid[r][c] = CHAR_MAP_EXTERNAL['*'] # BOX_ON_GOAL
  165. else:
  166. display_grid[r][c] = CHAR_MAP_EXTERNAL['$'] # BOX
  167.  
  168. # プレイヤーを表示
  169. r_p, c_p = self.player_pos
  170. display_grid[r_p][c_p] = CHAR_MAP_EXTERNAL['@'] # PLAYER
  171.  
  172. # 最終的な表示
  173. for r in range(self.height):
  174. for c in range(self.width):
  175. internal_char = display_grid[r][c]
  176. sys.stdout.write(self._get_external_char(internal_char))
  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. return key.hex() # For non-decodable keys like arrow keys
  231. else:
  232. fd = sys.stdin.fileno()
  233. old_settings = termios.tcgetattr(fd)
  234. try:
  235. tty.setraw(fd)
  236. key = sys.stdin.read(1)
  237. # 矢印キーのエスケープシーケンスを処理
  238. if key == '\x1b': # ESC key
  239. key += sys.stdin.read(2) # Read next two characters
  240. finally:
  241. termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
  242. return key
  243.  
  244. def run(self):
  245. while not self.is_won:
  246. self.draw()
  247. key = self.get_input()
  248.  
  249. if key in ['q', 'Q']:
  250. print("ゲーム中断。夜明けはまだ遠いか。")
  251. break
  252. elif key in ['r', 'R']:
  253. self.reset_game()
  254. print("ゲームリスタート。再び走り続けろ。")
  255. continue
  256.  
  257. dr, dc = self._get_move_delta(key)
  258. if dr != 0 or dc != 0:
  259. self.move_player(dr, dc)
  260. else:
  261. # 不正な入力の場合、特に何もせず次のループへ。
  262. # 「無意味な行動は慎むべきだ。」
  263. pass
  264.  
  265. if self.is_won:
  266. self.draw() # 勝利後の最終状態を描画
  267. print(f"\n勝利だ! {self.moves}回で夜明けを迎えたな。走り続けた甲斐があった。おめでとう。")
  268.  
  269. # サンプルマップ
  270. # 唐: 壁, 澤: 荷物, 貴: 格納場所, 洋: プレイヤー,  : 床
  271. SAMPLE_MAP = """
  272. 唐唐唐唐唐唐唐唐
  273. 唐   貴  唐
  274. 唐 澤唐唐澤 唐
  275. 唐  洋  貴唐
  276. 唐唐唐唐唐唐唐唐
  277. """
  278.  
  279. if __name__ == '__main__':
  280. game = SokobanGame(SAMPLE_MAP)
  281. game.run()
  282.  
  283. python sokoban.py
  284. 主要キー操作: WASD または 矢印キーで移動, Qで終了, Rでリスタート
  285.  
Add Comment
Please, Sign In to add comment