Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3e589f2
Add .gitattributes file to configure text handling and language detec…
Natuworkguy Aug 6, 2026
021fe67
Fix import order and ensure console compatibility in logger.py
Natuworkguy Aug 6, 2026
a3bea89
Move just_fix_windows_console() call to __init__.py and clean up impo…
Natuworkguy Aug 6, 2026
30fdb76
Refactor import of just_fix_windows_console() to improve clarity in _…
Natuworkguy Aug 6, 2026
4fb16b3
Refactor RGBType definition to include pygame.Color for improved type…
Natuworkguy Aug 6, 2026
be5bc86
Merge branch 'main' of https://github.com/Natuworkguy/ABS-Engine into…
Natuworkguy Aug 6, 2026
47cae41
Add destructor to Entity class for proper resource management
Natuworkguy Aug 6, 2026
cd6c84c
Fix formatting in RGBType
Natuworkguy Aug 7, 2026
6b347d9
Enhance destroy method documentation in Entity class to include error…
Natuworkguy Aug 7, 2026
6e2ae6b
Rephrase AI policy statement for clarity in AGENTS.md
Natuworkguy Aug 7, 2026
7a75005
Add 'visible' property to Entity class for rendering control
Natuworkguy Aug 7, 2026
818d6b0
Add visibility check to Text draw method for conditional rendering
Natuworkguy Aug 7, 2026
805cf49
Add center() method to Text entity
Natuworkguy Aug 7, 2026
4341820
Hoist center() into Entity base class
Natuworkguy Aug 7, 2026
8f78bb5
Add colorama for terminal title support in Game class
Natuworkguy Aug 8, 2026
342b10a
Add docstrings to center() methods in Entity and Text classes
Natuworkguy Aug 8, 2026
ea1eaec
Refactor logger function to improve readability and maintainability
Natuworkguy Aug 8, 2026
7463def
Use positional args for Font.render for pygame compatibility
Natuworkguy Aug 8, 2026
692b05a
Fix terminal title printing in Game class to avoid newline
Natuworkguy Aug 8, 2026
adb051f
Add newline after ABS Engine version line to separate it from logs
Natuworkguy Aug 8, 2026
939cb46
Add Release Version Guard workflow to enforce version bump on release…
Natuworkguy Aug 8, 2026
f5560b0
Fix formatting
Natuworkguy Aug 8, 2026
8441ad4
Upgrade github-script action to v8 in PR and release version guard wo…
Natuworkguy Aug 8, 2026
93150a3
Update pull request permissions from read to write in workflow files
Natuworkguy Aug 8, 2026
3578ae8
Enhance Release Version Guard to request changes and dismiss prior re…
Natuworkguy Aug 8, 2026
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
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* text=auto eol=lf

*.absp text linguist-language=JSON linguist-detectable=true diff=json
4 changes: 2 additions & 2 deletions .github/workflows/pr-target-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:

permissions:
contents: read
pull-requests: read
pull-requests: write
issues: write

jobs:
Expand Down Expand Up @@ -38,7 +38,7 @@ jobs:

- name: Comment warning on PR
if: steps.check.outputs.WARN == 'true'
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
await github.rest.issues.createComment({
Expand Down
110 changes: 110 additions & 0 deletions .github/workflows/release-version-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Release Version Guard

on:
pull_request:
types: [opened, reopened, synchronize, edited, labeled, unlabeled]

permissions:
contents: read
pull-requests: write
issues: write

jobs:
check-version-bump:
name: Check Version Bump
runs-on: ubuntu-latest
if: github.event.pull_request.base.ref == 'main'

steps:
- name: Determine release label
id: label
run: |
HAS_RELEASE_LABEL=false

for label in ${{ join(github.event.pull_request.labels.*.name, ' ') }}; do
if [ "$label" = "release" ]; then
HAS_RELEASE_LABEL=true
fi
done

echo "Has release label: $HAS_RELEASE_LABEL"
echo "release=$HAS_RELEASE_LABEL" >> "$GITHUB_OUTPUT"

- name: Checkout code
if: steps.label.outputs.release == 'true'
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Compare versions
if: steps.label.outputs.release == 'true'
id: compare
run: |
get_version() {
git show "$1:engine/version.py" \
| sed -n 's/^__version__[[:space:]]*=[[:space:]]*["'"'"']\(.*\)["'"'"'].*/\1/p'
}

OLD_VERSION=$(get_version "${{ github.event.pull_request.base.sha }}")
NEW_VERSION=$(get_version HEAD)

echo "Base (main) version: '$OLD_VERSION'"
echo "PR version: '$NEW_VERSION'"

if [ -z "$NEW_VERSION" ]; then
echo "Could not read __version__ from engine/version.py"
exit 1
fi

if [ "$NEW_VERSION" = "$OLD_VERSION" ]; then
echo "bumped=false" >> "$GITHUB_OUTPUT"
else
echo "bumped=true" >> "$GITHUB_OUTPUT"
fi

echo "old_version=$OLD_VERSION" >> "$GITHUB_OUTPUT"

- name: Request changes if version not bumped
if: steps.label.outputs.release == 'true' && steps.compare.outputs.bumped == 'false'
uses: actions/github-script@v8
with:
script: |
await github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
event: "REQUEST_CHANGES",
body: "@${{ github.event.pull_request.user.login }} This **release** PR does not bump `engine/version.py` (still `${{ steps.compare.outputs.old_version }}`). Update `__version__` before merging."
});

- name: Dismiss prior request-changes review once bumped
if: steps.label.outputs.release == 'true' && steps.compare.outputs.bumped == 'true'
uses: actions/github-script@v8
with:
script: |
const pull_number = context.payload.pull_request.number;
const { data: reviews } = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number,
});
for (const review of reviews) {
if (
review.user.login === "github-actions[bot]" &&
review.state === "CHANGES_REQUESTED"
) {
await github.rest.pulls.dismissReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number,
review_id: review.id,
message: "`engine/version.py` bumped — clearing the version guard.",
});
}
}

- name: Fail if version not bumped
if: steps.label.outputs.release == 'true' && steps.compare.outputs.bumped == 'false'
run: |
echo "Release PR must bump engine/version.py before merging."
exit 1
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<!-- markdownlint-disable MD041 -->

See ABS Engine's [AI Policy](CONTRIBUTING.md#ai-policy) for more information on
ABS Engine has a strict AI policy.

See [the AI Policy](CONTRIBUTING.md#ai-policy) for more information on
how AI may be used.
1 change: 1 addition & 0 deletions docs/scripting.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ The `engine.core.Entity` class has the following properties:
| `id` | Unique entity UUID | `str` |
| `get_colliding_entities` | Return a list of colliding entities | `Callable[[], list[Entity]]` |
| `destroy` | Destroy the entity | `Callable[[], None]` |
| `visible` | Weather to draw the entity or not | `bool` |

## Script Functions

Expand Down
4 changes: 4 additions & 0 deletions engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
"""
Core engine package
"""

import colorama

colorama.just_fix_windows_console()
47 changes: 40 additions & 7 deletions engine/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import tkinter.messagebox
import uuid
import os
import colorama

from typing import Optional, Any, Union

Expand All @@ -24,6 +25,7 @@

print(
f"ABS Engine v{version} (Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}, pygame {pygame.ver})"
"\n"
)


Expand Down Expand Up @@ -55,6 +57,8 @@ def __init__(
image (Optional[str]): Path to optional image file. Defaults to None.
"""

self.visible = True

self.x: int = x
self.y: int = y
self.width: int = width
Expand Down Expand Up @@ -139,6 +143,16 @@ def __repr__(self) -> str:

return f"<{self.__class__.__name__} at {hex(id(self))} with id {self.id}>"

def __del__(self) -> None:
"""
Destructor for the entity.
"""

try:
self.destroy()
except ValueError:
logger("Failed to destroy entity", status=LoggerStatus.WARNING)

def _collides_with(self, other: "Entity") -> bool:
"""
Check if this entity collides with another entity using AABB collision detection.
Expand All @@ -161,6 +175,18 @@ def _setparent(self, parent: "Scene") -> None:
"""
self.parent = parent

def center(self, pos: tuple[int, int]) -> None:
"""
Center the entity on a position.

Args:
pos (tuple[int, int]): The (x, y) point to center the entity on.
"""

self.x = pos[0] - self.width // 2
self.y = pos[1] - self.height // 2
self._update_rect()

def init(self) -> None:
"""
Call the init function in the script file if it exists.
Expand Down Expand Up @@ -215,10 +241,11 @@ def draw(self, surface: pygame.Surface) -> None:
surface (pygame.Surface): The surface to draw the entity on.
"""

if self.image is not None:
self.image.draw(surface, self.rect)
else:
pygame.draw.rect(surface, self.color, self.rect)
if self.visible:
if self.image is not None:
self.image.draw(surface, self.rect)
else:
pygame.draw.rect(surface, self.color, self.rect)

def get_colliding_entities(self) -> list["Entity"]:
"""
Expand All @@ -235,14 +262,17 @@ def get_colliding_entities(self) -> list["Entity"]:

def destroy(self) -> None:
"""
Destroy this entity
Destroy this entity.

Raises:
ValueError: If the entity cannot be removed from its parent.
"""

if self.parent is not None:
try:
self.parent.remove(self)
except ValueError:
logger("Invalid target for destruction", status=LoggerStatus.WARNING)
except ValueError as e:
raise ValueError("Invalid target for destruction") from e

self.parent = None

Expand Down Expand Up @@ -398,6 +428,9 @@ def __init__(
self.screen: pygame.Surface = pygame.display.set_mode(self.wsize, display_flags)
pygame.display.set_caption(title)

if sys.stdout.isatty():
print(colorama.ansi.set_title(title), end="")

self.set_icon(icon_path)

pygame.mouse.set_visible(cursor_visible)
Expand Down
22 changes: 16 additions & 6 deletions engine/core/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,20 @@ def _update_text_surface(self) -> None:
text, color, bgcolor, antialias, and position.
"""

self.text_surface = self.font.render(
self.text, antialias=self.antialias, color=self.color, bgcolor=self.bgcolor
)
self.text_surface = self.font.render(self.text, self.antialias, self.color, self.bgcolor)
self.text_rect = self.text_surface.get_rect(x=self.x, y=self.y)

def center(self, pos: tuple[int, int]) -> None:
"""
Center the text on a position and rebuild its rendered surface.

Args:
pos (tuple[int, int]): The (x, y) point to center the text on.
"""

super().center(pos)
self._update_text_surface()

def draw(self, surface: pygame.Surface) -> None:
"""
Draw the rendered text onto the given surface.
Expand All @@ -87,7 +96,8 @@ def draw(self, surface: pygame.Surface) -> None:
surface (pygame.Surface): The surface to draw the text on.
"""

if self.dynamic:
self._update_text_surface()
if self.visible:
if self.dynamic:
self._update_text_surface()

surface.blit(self.text_surface, self.text_rect)
surface.blit(self.text_surface, self.text_rect)
6 changes: 4 additions & 2 deletions engine/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

import pygame

from typing import Protocol
from typing import Union, Protocol

from .image import EntityImage

RGBType = tuple[int, int, int]
RGBType = Union[
tuple[int, int, int], pygame.Color # Add pygame.Color for type checkers
]
EntityImageType = EntityImage


Expand Down
18 changes: 13 additions & 5 deletions engine/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""

import inspect
import sys

from enum import Enum
from typing import Any
Expand Down Expand Up @@ -52,10 +53,17 @@ def logger(message: str, *, status: Status = Status.INFO) -> None:
"""

source = _get_caller_module().upper()
is_tty: bool = sys.stdout.isatty()

if status == Status.CRITICAL:
print(Fore.RED, end="")
elif status == Status.WARNING:
print(Fore.YELLOW, end="")
if is_tty:
if status == Status.CRITICAL:
print(Fore.RED, end="")
elif status == Status.WARNING:
print(Fore.YELLOW, end="")

print(f"({status.value}) {source}: {message}{Style.RESET_ALL}")
print(f"({status.value}) {source}: {message}", end="")

if is_tty:
print(Style.RESET_ALL, end="")

print()
Loading