Skip to content

Commit 396d86d

Browse files
Merge branch 'main' into issue#844-Refactor-project.js
2 parents a37dea9 + fa08c0f commit 396d86d

44 files changed

Lines changed: 4966 additions & 2455 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.editorconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.md]
10+
trim_trailing_whitespace = false

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -449,17 +449,17 @@ Once you feel comfortable, you can gradually try `level:intermediate`, `level:ad
449449

450450
## 🧪 Writing Tests
451451

452-
To maintain code quality and ensure zero external dependencies, we use the standard library's `unittest` framework.
452+
To maintain code quality, the repository uses `pytest` for its automated test suite.
453453

454454
1. **Test Directory**: All tests must be placed in the `tests/` directory at the root of the project.
455455
2. **File Naming**: Name your test file starting with `test_` (e.g., `test_armstrong.py`).
456-
3. **No External Modules**: Do not use external libraries like `pytest`. Stick strictly to `unittest`.
456+
3. **Test Runner**: Use `pytest` so local checks match the GitHub Actions workflow.
457457
4. **Mocking Inputs**: For CLI projects, use `subprocess` to provide input via `stdin`, or import helper methods directly.
458458

459459
### Running Tests Locally
460460
To run all tests and verify your changes:
461461
```bash
462-
python -m unittest discover -s tests -v
462+
python -m pytest tests/ -v
463463
```
464464
To run a syntax check across all files (similar to our CI):
465465
```bash

README.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
### _Learn Python by Building Fun, Interactive Games & Tools!_
88

9-
[![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
9+
[![Python Version](https://img.shields.io/badge/python-3.10--3.12-blue.svg)](https://www.python.org/downloads/)
1010
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
1111

1212
<p align="center">
@@ -80,14 +80,24 @@ python-mini-project/
8080

8181
### Prerequisites
8282

83-
- Python 3.10 or higher
83+
- Python 3.10, 3.11, or 3.12
8484
- Git (for cloning the repository)
8585
- pip (Python package manager)
8686

87+
### Encoding Note
88+
89+
This repository uses UTF-8 for documentation, issue templates, and project metadata. If emoji or box-drawing characters appear as mojibake in a Windows terminal, switch the terminal to UTF-8 before viewing or running the files.
90+
8791
### Installation Steps
8892

8993
#### 1. Clone the Repository
9094

95+
<div align="right">
96+
97+
<button class="copy-btn" data-copy="clone-command">📋 Copy</button>
98+
99+
</div>
100+
91101
```bash
92102
git clone https://github.com/steam-bell-92/python-mini-project.git
93103
cd python-mini-project
@@ -97,6 +107,12 @@ cd python-mini-project
97107

98108
For Linux/macOS:
99109

110+
<div align="right">
111+
112+
<button class="copy-btn" data-copy="linux-venv">📋 Copy</button>
113+
114+
</div>
115+
100116
```bash
101117
python3 -m venv venv
102118
source venv/bin/activate
@@ -118,11 +134,19 @@ venv\Scripts\Activate.ps1
118134

119135
#### 3. Install Dependencies
120136

137+
<div align="right">
138+
139+
<button class="copy-btn" data-copy="install-command">📋 Copy</button>
140+
141+
</div>
142+
121143
```bash
122144
pip install --upgrade pip
123145
pip install -r requirements.txt
124146
```
125147

148+
The dependency set is tested on Python 3.10 through 3.12. Newer Python releases may not have compatible wheels for every pinned dependency yet.
149+
126150
### Running Command-Line Projects
127151

128152
Each Python project in `games/`, `math/`, and `utilities/` folders can be run independently.
@@ -161,6 +185,12 @@ The web app requires Node.js and npm:
161185

162186
#### Steps to Run Web App
163187

188+
<div align="right">
189+
190+
<button class="copy-btn" data-copy="web-command">📋 Copy</button>
191+
192+
</div>
193+
164194
```bash
165195
cd web-app
166196
npm install
@@ -173,14 +203,20 @@ The app will open at `http://localhost:3000` (or your configured port).
173203

174204
To verify that the projects work correctly, run the test suite:
175205

206+
<div align="right">
207+
208+
<button class="copy-btn" data-copy="pytest-command">📋 Copy</button>
209+
210+
</div>
211+
176212
```bash
177213
pytest tests/ -v
178214
```
179215

180216
For specific test file:
181217

182218
```bash
183-
pytest tests/test_armstrong.py -v
219+
pytest tests/test_smoke.py -v
184220
```
185221

186222
### Development Workflow

games/2048-Game/2048-Game.py

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,17 @@ def __init__(self, root):
7575
)
7676
self.restart_button.grid(pady=5)
7777

78+
self.reset_high_score_button = tk.Button(
79+
root,
80+
text="Reset High Score",
81+
font=("Arial", 12, "bold"),
82+
command=self.reset_high_score
83+
)
84+
self.reset_high_score_button.grid(pady=5)
85+
7886
self.instruction_label = tk.Label(
7987
root,
80-
text="🎮 Controls: ← ↑ → ↓ | Merge same numbers | Goal: 2048 🎯",
88+
text="🎮 Controls: WASD / ← ↑ → ↓ | Merge same numbers | Goal: 2048 🎯",
8189
font=("Arial", 10),
8290
fg="#6a635b",
8391
)
@@ -87,6 +95,8 @@ def __init__(self, root):
8795
self.board = [[0] * GRID_SIZE for _ in range(GRID_SIZE)]
8896

8997
self.game_over_label = None
98+
self.win_label = None
99+
self.game_won = False
90100

91101
self.create_grid()
92102
self.start_new_game()
@@ -107,6 +117,18 @@ def save_high_score(self):
107117
except OSError as e:
108118
print(f"Warning: Could not save high score: {e}")
109119

120+
def reset_high_score(self):
121+
"""Reset the high score to 0 and clear the saved high score file."""
122+
self.high_score = 0
123+
try:
124+
if HIGH_SCORE_PATH.exists():
125+
HIGH_SCORE_PATH.unlink()
126+
except OSError as e:
127+
print(f"Warning: Could not delete high score file: {e}")
128+
self.score_label.configure(
129+
text=f"Score: {self.score} High Score: {self.high_score}"
130+
)
131+
110132
def create_grid(self):
111133
for row in range(GRID_SIZE):
112134
row_cells = []
@@ -146,6 +168,7 @@ def start_new_game(self):
146168
self.board = [[0] * GRID_SIZE for _ in range(GRID_SIZE)]
147169
self.score = 0
148170
self.moves_left = TOTAL_MOVES
171+
self.game_won = False
149172

150173
self.add_new_tile()
151174
self.add_new_tile()
@@ -261,44 +284,58 @@ def check_game_over(self):
261284

262285
return True
263286

264-
def handle_keypress(self, event):
265-
key = event.keysym
266-
moved = False
267-
268-
if key == "Left":
269-
moved = self.move_left()
287+
def check_win(self):
288+
return any(2048 in row for row in self.board)
270289

271-
elif key == "Right":
272-
moved = self.move_right()
273-
274-
elif key == "Up":
275-
moved = self.move_up()
276-
277-
elif key == "Down":
278-
moved = self.move_down()
279-
280-
else:
290+
def handle_keypress(self, event):
291+
key = event.keysym.lower() if len(event.keysym) == 1 else event.keysym
292+
move_map = {
293+
"Left": self.move_left, "a": self.move_left,
294+
"Right": self.move_right, "d": self.move_right,
295+
"Up": self.move_up, "w": self.move_up,
296+
"Down": self.move_down, "s": self.move_down,
297+
}
298+
if key not in move_map:
281299
return
282-
283-
300+
301+
moved = move_map[key]()
284302

285303
if moved:
286304
self.add_new_tile()
287-
self.moves_left -=1
305+
self.moves_left -= 1
288306

289307
if self.score > self.high_score:
290308
self.high_score = self.score
291309
self.save_high_score()
292310

293311
self.update_grid()
294312

295-
# FIXED BUG:
296-
# Game over is now checked even when no movement happens
297-
if self.check_game_over():
298-
self.game_over()
299-
if self.moves_left<=0:
313+
if not self.game_won and self.check_win():
314+
self.you_win()
315+
return
316+
317+
# Check for game over even if no movement happens
318+
if self.check_game_over() or self.moves_left <= 0:
300319
self.game_over()
301320

321+
def you_win(self):
322+
323+
if self.game_won:
324+
return
325+
326+
self.game_won = True
327+
328+
self.root.unbind("<Key>")
329+
330+
self.win_label = tk.Label(
331+
self.root,
332+
text="🎉 YOU WIN! 🎉",
333+
font=("Arial", 24, "bold"),
334+
fg="green"
335+
)
336+
337+
self.win_label.grid(pady=10)
338+
302339
def game_over(self):
303340

304341
# Prevent duplicate labels
@@ -324,6 +361,11 @@ def restart_game(self):
324361
self.game_over_label.destroy()
325362
self.game_over_label = None
326363

364+
# Remove old win label
365+
if self.win_label is not None:
366+
self.win_label.destroy()
367+
self.win_label = None
368+
327369
self.start_new_game()
328370

329371
# Re-enable keyboard controls
@@ -335,5 +377,6 @@ def main():
335377
game = Game2048(root)
336378
root.mainloop()
337379

380+
338381
if __name__ == "__main__":
339382
main()

0 commit comments

Comments
 (0)