Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
28bf099
Setup project with basic Github Actions workflow
xl4624 Oct 25, 2025
cf0d7c2
Merge pull request #1 from swe-students-fall2025/project-setup
apoorvib Oct 25, 2025
185466f
Implement colorize_ascii()
xl4624 Oct 27, 2025
d1c0898
Merge pull request #2 from swe-students-fall2025/colorize-ascii
apoorvib Oct 27, 2025
835120b
basic function and unit test for get command
Morinzzz Oct 27, 2025
8b9bc2a
Merge pull request #3 from swe-students-fall2025/Morin
yf2685-beep Oct 27, 2025
c6fcd61
Add animation feature and CLI trigger, with tests
yf2685-beep Oct 30, 2025
6d62e47
Fix animate_scroll frame count and scrolling logic
yf2685-beep Oct 30, 2025
c5506fd
Merge pull request #5 from swe-students-fall2025/fix/animate-scroll
yf2685-beep Oct 31, 2025
9fc97d3
Update pyproject.toml name for TestPyPI
yf2685-beep Oct 31, 2025
5a413a1
update package name in pyproject.toml
yf2685-beep Oct 31, 2025
6347579
Merge pull request #6 from swe-students-fall2025/fix/pyproject-update
xl4624 Oct 31, 2025
172c2ef
Fix animation scroll logic in animate.py
yf2685-beep Nov 2, 2025
a66d5d9
Merge pull request #7 from swe-students-fall2025/feature/animate-fix
Morinzzz Nov 2, 2025
4a99f94
Finalized font designs for A-Z and 0-9 characters
apoorvib Nov 3, 2025
9c28231
Created some sample commands, developed rendering file, modified font…
apoorvib Nov 3, 2025
f4c5358
comment part of the tests
Morinzzz Nov 3, 2025
7f8d607
Merge pull request #9 from swe-students-fall2025/feature/animate-fix
xl4624 Nov 3, 2025
ed84ab7
fix CI error temporarily
Morinzzz Nov 3, 2025
565c3f4
Merge pull request #8 from swe-students-fall2025/feature/render
xl4624 Nov 3, 2025
6680567
Added CI badge to the README file
Nov 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI

on:
push:
branches: [pipfile-experiment]
pull_request:
branches: [pipfile-experiment]

jobs:
test:
runs-on: ubuntu-latest
env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PIP_ROOT_USER_ACTION: "ignore"
strategy:
fail-fast: false
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies and pytest
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
python -m pytest

build:
runs-on: ubuntu-latest
env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PIP_ROOT_USER_ACTION: "ignore"
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Build sdist and wheel
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
python -m build
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,6 @@ dmypy.json

# Cython debug symbols
cython_debug/

# Project proposal file
Project_3_proposal.docx
14 changes: 14 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
minecraft-textcraft = {extras = ["dev"], file = ".", editable = true}

[dev-packages]
pytest = "*"
build = "*"

[requires]
python_version = "3"
84 changes: 84 additions & 0 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
# Python Package Exercise
[![CI](https://github.com/swe-students-fall2025/3-python-package-brio/actions/workflows/ci.yml/badge.svg)](https://github.com/swe-students-fall2025/3-python-package-brio/actions/workflows/ci.yml)
# minecraft-textcraft

A Python package that converts text into blocky Minecraft-style ASCII art, with support for special inline commands like \sword, \heart, and \diamond. Commands are center-aligned with text for a professional, visually appealing output.

# Quickstart

To setup:
```bash
git clone git@github.com:swe-students-fall2025/3-python-package-brio.git
cd 3-python-package-brio
pipenv shell
pipenv install -e ".[dev]"
```

To build:
```bash
pipenv run python -m build
```
Artifacts will be in `dist/`.

To run:
```bash
pipenv run minecraft-textcraft
```

To run tests:
```bash
pipenv run pytest
```

An exercise to create a Python package, build it, test it, distribute it, and use it. See [instructions](./instructions.md) for details.
20 changes: 20 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[build-system]
requires = ["setuptools>=77.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "minecraft-textcraft"
version = "0.0.2"
description = "A Python package that converts text into blocky Minecraft-style ASCII art..."
readme = "README.md"
license = "GPL-3.0-or-later"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python :: 3",
"Intended Audience :: Education",
"Operating System :: OS Independent",
]
[project.optional-dependencies]
dev = ["pytest", "build"]
[project.scripts]
minecraft-textcraft = "minecraft_textcraft.__main__:main"
7 changes: 7 additions & 0 deletions src/minecraft_textcraft/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__version__ = "0.0.1"

from .add import add
from .colorize_ascii import colorize_ascii, Color
from .commands import getCommand, getCommandByName, listCategories, listCommands
from .animate import animate_typewriter, animate_scroll, animate_wave, play_animation
from .render import render, renderTextOnly, renderCommandOnly, renderTextAndCommands
44 changes: 44 additions & 0 deletions src/minecraft_textcraft/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from .colorize_ascii import colorize_ascii, Color
try:
from .animate import animate_typewriter, animate_scroll, animate_wave, play_animation
_ANIM_AVAILABLE = True
except Exception:
_ANIM_AVAILABLE = False

import argparse
HELLO = """
█ █ █████ █ █ █████
█ █ █ █ █ █ █
█████ ████ █ █ █ █
█ █ █ █ █ █ █
█ █ █████ █████ █████ █████
"""


def main():
p = argparse.ArgumentParser(add_help=False)
p.add_argument("--effect", choices=["type", "scroll", "wave"])
p.add_argument("--fps", type=int, default=12)
p.add_argument("--color", default="RED")
args, _ = p.parse_known_args()

if not args.effect or not _ANIM_AVAILABLE:
# 保持你原来的输出
print(colorize_ascii(HELLO.strip("\n"), Color.RED))
return

# 有 --effect 时才播放动画
color = Color.from_string(args.color)
art = HELLO.strip("\n")
if args.effect == "type":
frames = animate_typewriter(art, color=color, cps=12)
elif args.effect == "scroll":
frames = animate_scroll(art, color=color, cols_per_frame=2, loops=2)
else: # wave
frames = animate_wave(art, color=color, amplitude=2, period_cols=10, frames=36)
play_animation(frames, fps=args.fps)



if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions src/minecraft_textcraft/add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def add(a: int, b: int) -> int:
return a + b
98 changes: 98 additions & 0 deletions src/minecraft_textcraft/animate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# src/minecraft_textcraft/animate.py
from typing import Generator, Iterable, Optional
from .colorize_ascii import colorize_ascii, Color

def _normalize(ascii_art: str) -> list[str]:
lines = ascii_art.splitlines()
if not lines:
return []
width = max(len(ln) for ln in lines)
return [ln.ljust(width) for ln in lines]

def animate_typewriter(
ascii_art: str,
color: Optional[Color] = None,
cps: int = 10, # characters per second
) -> Generator[str, None, None]:
lines = _normalize(ascii_art)
if not lines:
yield ""
return
width = len(lines[0])
# speed: 12 fps
step = max(1, int(round(cps / 12)))
shown = 0
while shown <= width:
frame = "\n".join(ln[:shown] for ln in lines)
yield colorize_ascii(frame, color) if color else frame
shown += step
full = "\n".join(lines)
yield colorize_ascii(full, color) if color else full

def animate_scroll(
ascii_art: str,
color: Optional[Color] = None,
cols_per_frame: int = 1,
loops: int = 1,
) -> Generator[str, None, None]:
lines = _normalize(ascii_art)
if not lines:
yield ""
return
cols_per_frame = max(1, int(cols_per_frame))
width = len(lines[0])
pad = " " * cols_per_frame
padded = [pad + ln + pad for ln in lines] # width = width + 2*cols
total_w = width + 2 * cols_per_frame

def colorize_if_needed(s: str) -> str:
return colorize_ascii(s, color) if color else s

def one_loop() -> Generator[str, None, None]:
for start in range(0, total_w):
frame_cols = [row[start:start + cols_per_frame] for row in padded]
yield colorize_if_needed("\n".join(frame_cols))

loops = max(1, int(loops))
for _ in range(loops):
yield from one_loop()


def animate_wave(
ascii_art: str,
color: Optional[Color] = None,
amplitude: int = 2,
period_cols: int = 10,
frames: int = 24,
) -> Generator[str, None, None]:

import math
base = _normalize(ascii_art)
if not base:
yield ""
return
h = len(base)
w = len(base[0])
pad = amplitude + 1
for t in range(max(1, frames)):
canvas_h = h + 2 * pad
canvas = [[" "] * w for _ in range(canvas_h)]
for r in range(h):
for c, ch in enumerate(base[r]):
phase = 2 * math.pi * (c / max(1, period_cols)) + 2 * math.pi * (t / max(1, frames))
offset = int(round(amplitude * math.sin(phase)))
rr = r + pad + offset
if 0 <= rr < canvas_h:
canvas[rr][c] = ch
frame = "\n".join("".join(row) for row in canvas)
yield colorize_ascii(frame, color) if color else frame

def play_animation(frames: Iterable[str], fps: int = 12) -> None:
import time
if fps <= 0:
fps = 12
delay = 1.0 / fps
for f in frames:
print("\033[2J\033[H", end="") # clear screen + cursor home
print(f)
time.sleep(delay)
24 changes: 24 additions & 0 deletions src/minecraft_textcraft/colorize_ascii.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from enum import Enum

ESC = "\x1b"

class Color(Enum):
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
DEFAULT = 0

def __str__(self):
return f"{ESC}[{self.value}m"

@classmethod
def from_string(cls, s: str) -> "Color":
try:
return cls[s.upper()]
except KeyError:
return cls.DEFAULT


def colorize_ascii(ascii: str, color: Color):
return f"{color}{ascii}{Color.DEFAULT}"
Loading
Loading