cli-othello 1.0.2__tar.gz → 1.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ MITライセンス
2
+
3
+ Copyright (c) 2026 Lapius7bot Technology Co.
4
+
5
+ 本パッケージおよび関連するドキュメントファイル(以下「本パッケージ」)のコピーを取得したすべての人に対し、本パッケージを無制限に利用する権利を無償で付与します。これには、使用、複製、改変、統合、公開、配布、サブライセンス、および/または販売する権利が含まれますが、これらに限定されません。ただし、以下の条件を遵守することを条件とします。
6
+ 上記の著作権表示および本許可通知は、本パッケージのすべてのコピーまたはその大部分に記載する必要があります。
7
+ 本パッケージは「現状のまま」使用、その他の取り扱いに起因または関連して生じる請求、損害、その他の責任について、契約違反、不法行為、その他の理由にかかわらず、一切の責任を負いません。提供されるものであり、明示的または黙示的を問わず、商品性、特定目的への適合性、非侵害性に関する保証を含むいかなる保証も行いません。いかなる場合においても、著作者または著作権者は、本パッケージまたはその
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-othello
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Play Othello (Reversi) in your terminal against a 5-level AI
5
5
  Author-email: Lapius7 <20071209ryo@gmail.com>
6
6
  License: MIT
@@ -8,7 +8,9 @@ Project-URL: Homepage, https://github.com/Lapius7/cli-othello
8
8
  Project-URL: Repository, https://github.com/Lapius7/cli-othello
9
9
  Requires-Python: >=3.9
10
10
  Description-Content-Type: text/markdown
11
+ License-File: LICENSE
11
12
  Requires-Dist: windows-curses; platform_system == "Windows"
13
+ Dynamic: license-file
12
14
 
13
15
  # cli-othello
14
16
 
@@ -17,10 +19,22 @@ Requires-Dist: windows-curses; platform_system == "Windows"
17
19
 
18
20
  ## インストール
19
21
 
22
+ ### npm(推奨)
23
+
24
+ ```bash
25
+ npm i -g @lapius/cli-othello
26
+ ```
27
+
28
+ `othello` コマンドが入ります(Node.js 18 以上と Python 3.9 以上が必要)。
29
+
30
+ ### pip
31
+
20
32
  ```bash
21
33
  pip install cli-othello
22
34
  ```
23
35
 
36
+ Windows では curses を使うために `windows-curses` が必要です。pip で入れた場合は自動で入ります。npm で入れた場合は `py -m pip install windows-curses` で入れてください。
37
+
24
38
  ## 使い方
25
39
 
26
40
  ```bash
@@ -58,8 +72,8 @@ othello --level 3
58
72
 
59
73
  ## 必要環境
60
74
 
61
- - Python 3.9以上
62
- - Windowsの場合は `windows-curses` が自動的に依存関係としてインストールされます
75
+ - Python 3.9以上(npm で入れる場合は Node.js 18 以上も)
76
+ - 端末は 57列 x 36行 以上
63
77
 
64
78
  ## ライセンス
65
79
 
@@ -5,10 +5,22 @@
5
5
 
6
6
  ## インストール
7
7
 
8
+ ### npm(推奨)
9
+
10
+ ```bash
11
+ npm i -g @lapius/cli-othello
12
+ ```
13
+
14
+ `othello` コマンドが入ります(Node.js 18 以上と Python 3.9 以上が必要)。
15
+
16
+ ### pip
17
+
8
18
  ```bash
9
19
  pip install cli-othello
10
20
  ```
11
21
 
22
+ Windows では curses を使うために `windows-curses` が必要です。pip で入れた場合は自動で入ります。npm で入れた場合は `py -m pip install windows-curses` で入れてください。
23
+
12
24
  ## 使い方
13
25
 
14
26
  ```bash
@@ -46,8 +58,8 @@ othello --level 3
46
58
 
47
59
  ## 必要環境
48
60
 
49
- - Python 3.9以上
50
- - Windowsの場合は `windows-curses` が自動的に依存関係としてインストールされます
61
+ - Python 3.9以上(npm で入れる場合は Node.js 18 以上も)
62
+ - 端末は 57列 x 36行 以上
51
63
 
52
64
  ## ライセンス
53
65
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cli-othello"
7
- version = "1.0.2"
7
+ version = "1.0.4"
8
8
  description = "Play Othello (Reversi) in your terminal against a 5-level AI"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,3 +1,3 @@
1
1
  """cli-othello: play Othello in your terminal."""
2
2
 
3
- __version__ = "1.0.2"
3
+ __version__ = "1.0.4"
@@ -9,15 +9,37 @@ from .board import BLACK, EMPTY, SIZE, WHITE, Board, opponent
9
9
 
10
10
  PLAYER_NAMES = {BLACK: "黒", WHITE: "白"}
11
11
 
12
- # Each cell is rendered as CELL_W columns x CELL_H rows so it reads as
13
- # roughly square in a terminal (chars are about twice as tall as wide).
12
+ # Each cell's interior is CELL_W columns x CELL_H rows, not counting the
13
+ # grid lines drawn around it, so it reads as roughly square in a terminal
14
+ # (chars are about twice as tall as wide).
14
15
  CELL_W = 5
15
16
  CELL_H = 2
16
17
 
17
- DISC_BLACK = "●"
18
- DISC_WHITE = "○"
18
+ # Round glyphs such as "⬤"/"◯" have ambiguous display width: some
19
+ # terminal/font combinations render them as 1 column, others as 2, and
20
+ # this can't be reliably detected from Python (confirmed: wcwidth's
21
+ # reported width didn't match actual rendering in testing). Discs are
22
+ # therefore drawn as a half-width letter on a solid background instead,
23
+ # which always lines up with the surrounding grid.
24
+ DISC_BLACK = "@"
25
+ DISC_WHITE = "O"
26
+ DISC_WIDTH = 1
19
27
  HINT_MARK = "+"
20
28
 
29
+
30
+ def _center_display(glyph: str, glyph_width: int, total_width: int) -> str:
31
+ """Center `glyph` (which occupies `glyph_width` display columns) inside
32
+ a field of `total_width` columns, using plain spaces for padding."""
33
+ pad = total_width - glyph_width
34
+ left = pad // 2
35
+ right = pad - left
36
+ return " " * left + glyph + " " * right
37
+
38
+ # Box-drawing characters for the grid lines between cells.
39
+ _TL, _TR, _BL, _BR = "┌", "┐", "└", "┘"
40
+ _H, _V = "─", "│"
41
+ _T_DOWN, _T_UP, _T_RIGHT, _T_LEFT, _CROSS = "┬", "┴", "├", "┤", "┼"
42
+
21
43
  COLOR_BOARD = 1
22
44
  COLOR_BLACK = 2
23
45
  COLOR_WHITE = 3
@@ -60,7 +82,7 @@ def select_level(stdscr: "curses._CursesWindow") -> int:
60
82
  if max_y < required_rows or max_x < required_cols:
61
83
  _draw_too_small(stdscr)
62
84
  continue
63
- stdscr.addstr(1, 2, "♟ CLI OTHELLO", curses.color_pair(COLOR_HEADER) | curses.A_BOLD)
85
+ stdscr.addstr(1, 2, "CLI OTHELLO", curses.color_pair(COLOR_HEADER) | curses.A_BOLD)
64
86
  stdscr.addstr(3, 2, "対戦するAIの強さを選んでください", curses.A_BOLD)
65
87
  for i in range(MIN_LEVEL, MAX_LEVEL + 1):
66
88
  label = LEVEL_LABELS[i]
@@ -99,25 +121,29 @@ def _cell_fill_attr(cell: int, is_cursor: bool) -> int:
99
121
  return curses.color_pair(COLOR_BOARD)
100
122
 
101
123
 
102
- def _cell_center_attr(cell: int, is_cursor: bool, is_hint: bool) -> tuple[int, str]:
103
- """Return (attribute, character) for the center row of a cell."""
124
+ def _cell_center_attr(cell: int, is_cursor: bool, is_hint: bool) -> tuple[int, str, int]:
125
+ """Return (attribute, character, display_width) for the center row of
126
+ a cell."""
104
127
  fill = _cell_fill_attr(cell, is_cursor)
105
128
  if cell == BLACK:
106
- return fill, DISC_BLACK
129
+ return fill, DISC_BLACK, DISC_WIDTH
107
130
  if cell == WHITE:
108
- return fill, DISC_WHITE
131
+ return fill, DISC_WHITE, DISC_WIDTH
109
132
  if is_hint:
110
133
  attr = curses.color_pair(COLOR_CURSOR_EMPTY if is_cursor else COLOR_HINT) | curses.A_BOLD
111
- return attr, HINT_MARK
112
- return fill, " "
134
+ return attr, HINT_MARK, 1
135
+ return fill, " ", 0
113
136
 
114
137
 
115
138
  def _required_size() -> tuple[int, int]:
116
139
  """Return (rows, cols) needed to draw the board and panel, plus a
117
140
  1-line/1-column margin (curses can refuse writes to the very last
118
141
  row/column of the screen)."""
119
- rows = 3 + 1 + SIZE * CELL_H + 6 + 1
120
- cols = 4 + 3 + SIZE * CELL_W + 1
142
+ grid_rows = 1 + SIZE * (CELL_H + 1)
143
+ grid_cols = 1 + SIZE * (CELL_W + 1)
144
+ row_label_w = 3
145
+ rows = 3 + 1 + grid_rows + 6 + 1
146
+ cols = 4 + row_label_w + grid_cols + 1
121
147
  return rows, cols
122
148
 
123
149
 
@@ -153,7 +179,7 @@ def _draw_board(
153
179
  return False
154
180
 
155
181
  stdscr.erase()
156
- stdscr.addstr(top, 2, "♟ CLI OTHELLO", curses.color_pair(COLOR_HEADER) | curses.A_BOLD)
182
+ stdscr.addstr(top, 2, "CLI OTHELLO", curses.color_pair(COLOR_HEADER) | curses.A_BOLD)
157
183
  stdscr.addstr(
158
184
  top + 1,
159
185
  2,
@@ -164,37 +190,66 @@ def _draw_board(
164
190
  board_top = top + 3
165
191
  board_left = 4
166
192
  grid_attr = curses.color_pair(COLOR_GRID)
193
+ row_label_w = 3
167
194
 
168
- # Column labels (A-H), centered above each cell.
169
- header = " " * 3
195
+ # Column labels (A-H), centered above each cell (account for the
196
+ # leading grid line and the row-label gutter).
197
+ header = " " * row_label_w + " "
170
198
  for col in range(SIZE):
171
- header += "ABCDEFGH"[col].center(CELL_W)
199
+ header += "ABCDEFGH"[col].center(CELL_W) + " "
172
200
  stdscr.addstr(board_top, board_left, header, curses.A_BOLD)
173
201
 
174
- board_width = 3 + SIZE * CELL_W
202
+ grid_top = board_top + 1
203
+ grid_left = board_left + row_label_w
204
+ grid_cols = 1 + SIZE * (CELL_W + 1)
205
+ grid_rows = 1 + SIZE * (CELL_H + 1)
206
+ board_width = row_label_w + grid_cols
207
+
208
+ # Draw the grid lines (the lattice of cell borders) first.
209
+ for r in range(SIZE + 1):
210
+ y = grid_top + r * (CELL_H + 1)
211
+ if r == 0:
212
+ left, mid, right = _TL, _T_DOWN, _TR
213
+ elif r == SIZE:
214
+ left, mid, right = _BL, _T_UP, _BR
215
+ else:
216
+ left, mid, right = _T_RIGHT, _CROSS, _T_LEFT
217
+ line = left + (_H * CELL_W + mid) * SIZE
218
+ line = line[:-1] + right
219
+ stdscr.addstr(y, grid_left, line, grid_attr)
220
+ if r < SIZE:
221
+ for line_offset in range(1, CELL_H + 1):
222
+ y2 = y + line_offset
223
+ stdscr.addstr(y2, grid_left, _V, grid_attr)
224
+ stdscr.addstr(y2, grid_left + grid_cols - 1, _V, grid_attr)
225
+ for c in range(1, SIZE):
226
+ x = grid_left + c * (CELL_W + 1)
227
+ stdscr.addstr(y2, x, _V, grid_attr)
228
+
229
+ # Row labels (1-8), centered next to each cell.
175
230
  for row in range(SIZE):
176
- for line in range(CELL_H):
177
- y = board_top + 1 + row * CELL_H + line
178
- if line == CELL_H // 2:
179
- row_label = f"{row + 1:>2} "
180
- else:
181
- row_label = " "
182
- stdscr.addstr(y, board_left, row_label, curses.A_BOLD)
183
- for col in range(SIZE):
184
- cell = board.get(row, col)
185
- is_cursor = (row, col) == cursor
186
- is_hint = cell == EMPTY and (row, col) in legal
187
- x = board_left + 3 + col * CELL_W
188
-
189
- if line == CELL_H // 2:
190
- attr, ch = _cell_center_attr(cell, is_cursor, is_hint)
191
- text = ch.center(CELL_W)
231
+ y = grid_top + 1 + row * (CELL_H + 1) + (CELL_H - 1) // 2
232
+ stdscr.addstr(y, board_left, f"{row + 1:>2} ", curses.A_BOLD)
233
+
234
+ # Fill each cell's interior (background + disc/hint glyph).
235
+ for row in range(SIZE):
236
+ cell_top = grid_top + 1 + row * (CELL_H + 1)
237
+ for col in range(SIZE):
238
+ cell = board.get(row, col)
239
+ is_cursor = (row, col) == cursor
240
+ is_hint = cell == EMPTY and (row, col) in legal
241
+ x = grid_left + 1 + col * (CELL_W + 1)
242
+ for line in range(CELL_H):
243
+ y = cell_top + line
244
+ if line == (CELL_H - 1) // 2:
245
+ attr, ch, width = _cell_center_attr(cell, is_cursor, is_hint)
246
+ text = _center_display(ch, width, CELL_W)
192
247
  else:
193
248
  attr = _cell_fill_attr(cell, is_cursor)
194
249
  text = " " * CELL_W
195
250
  stdscr.addstr(y, x, text, attr)
196
251
 
197
- board_bottom = board_top + 1 + SIZE * CELL_H
252
+ board_bottom = grid_top + grid_rows - 1
198
253
  panel_left = board_left
199
254
  panel_width = board_width
200
255
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-othello
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Play Othello (Reversi) in your terminal against a 5-level AI
5
5
  Author-email: Lapius7 <20071209ryo@gmail.com>
6
6
  License: MIT
@@ -8,7 +8,9 @@ Project-URL: Homepage, https://github.com/Lapius7/cli-othello
8
8
  Project-URL: Repository, https://github.com/Lapius7/cli-othello
9
9
  Requires-Python: >=3.9
10
10
  Description-Content-Type: text/markdown
11
+ License-File: LICENSE
11
12
  Requires-Dist: windows-curses; platform_system == "Windows"
13
+ Dynamic: license-file
12
14
 
13
15
  # cli-othello
14
16
 
@@ -17,10 +19,22 @@ Requires-Dist: windows-curses; platform_system == "Windows"
17
19
 
18
20
  ## インストール
19
21
 
22
+ ### npm(推奨)
23
+
24
+ ```bash
25
+ npm i -g @lapius/cli-othello
26
+ ```
27
+
28
+ `othello` コマンドが入ります(Node.js 18 以上と Python 3.9 以上が必要)。
29
+
30
+ ### pip
31
+
20
32
  ```bash
21
33
  pip install cli-othello
22
34
  ```
23
35
 
36
+ Windows では curses を使うために `windows-curses` が必要です。pip で入れた場合は自動で入ります。npm で入れた場合は `py -m pip install windows-curses` で入れてください。
37
+
24
38
  ## 使い方
25
39
 
26
40
  ```bash
@@ -58,8 +72,8 @@ othello --level 3
58
72
 
59
73
  ## 必要環境
60
74
 
61
- - Python 3.9以上
62
- - Windowsの場合は `windows-curses` が自動的に依存関係としてインストールされます
75
+ - Python 3.9以上(npm で入れる場合は Node.js 18 以上も)
76
+ - 端末は 57列 x 36行 以上
63
77
 
64
78
  ## ライセンス
65
79
 
@@ -1,3 +1,4 @@
1
+ LICENSE
1
2
  README.md
2
3
  pyproject.toml
3
4
  src/cli_othello/__init__.py
File without changes