Skip to content

Modernize ircnukes: Python 3, typed, irc library, unit tests#1

Draft
giannitedesco with Copilot wants to merge 7 commits into
masterfrom
copilot/modernize-irc-bot-code
Draft

Modernize ircnukes: Python 3, typed, irc library, unit tests#1
giannitedesco with Copilot wants to merge 7 commits into
masterfrom
copilot/modernize-irc-bot-code

Conversation

Copilot AI commented Feb 28, 2026

Copy link
Copy Markdown

The codebase was written for Python 2 using a defunct irclib library. This PR ports everything to Python 3.14-compatible code, wires up the modern irc (jaraco) library, adds mypy --strict type annotations, docstrings, and a unit test suite.

nukes/ package

  • Fixed relative imports (from globals import *from .globals import *); explicit __all__ in __init__.py
  • Replaced has_key(), bare filter()/map(), except E, e: syntax throughout
  • Full type annotations on all classes and functions; circular imports resolved via TYPE_CHECKING guards
  • All game classes renamed to PascalCase: Card, Deck, Player, Game, Warhead, Missile, Bomber, Propaganda; backward-compatible lowercase aliases retained
  • Game/player state constants replaced with GameState(IntEnum) and PlayerState(IntEnum)
  • Nuke yield constants replaced with NukeYield(IntEnum) carrying a .popdead property (base population kill); standalone _BODYCOUNTS dict removed
  • Code passes flake8 (with pep8-naming and flake8-bandit); asserts replaced with proper exceptions; # noqa: S311 on intentional game-randomness random.randint calls

nukebot.py — irc library migration

  • Rewritten as irc.bot.SingleServerIRCBot subclass; event handlers use on_* methods
  • IRC event API updated: ev.source / ev.target / ev.arguments[0] (attributes, not calls)
  • Bot config via environment variables (NUKEBOT_NICK, NUKEBOT_HOST, NUKEBOT_CHAN, etc.)
  • TokenBucket ported to use time.monotonic()
  • IrcNukes class (renamed from ircnukes) wraps the game engine

ircnukes.py

  • irclib.irc_lowerirc.strings.lower
  • privmsg callable typed as Callable[[str, str], None]; None-safe guards added

Testing & tooling

  • 36 unit tests in tests/test_game.py covering player, deck, game state machine, all card types, exceptions, and the IRC wrapper
  • requirements.txt: irc>=20.5.0, mypy>=1.0.0
  • .gitignore added
# Before (Python 2, irclib)
class NukeBot:
    irc = irclib.IRC()
    irc.add_global_handler('pubmsg', irc_msg_pub)

# After (Python 3, irc.bot)
class NukeBot(irc.bot.SingleServerIRCBot):
    def on_pubmsg(self, conn: irc.client.ServerConnection, ev: irc.client.Event) -> None:
        nick = irc.client.NickMask(ev.source).nick
        if ev.arguments[0].startswith("!"):
            self._handle_pub(nick, ev.target, ev.arguments[0][1:])
Original prompt

Modernize the code for the latest python 3.14. Use an existing irc library to implement the bot. Use mypy --strict type hints, and document all the code and write unit tests.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits February 28, 2026 12:21
- Port all code from Python 2 to Python 3
- Add mypy --strict compatible type hints throughout
- Add Google-style docstrings to all public APIs
- Replace irclib with the modern irc (jaraco/irc) library
- Refactor nukebot.py as irc.bot.SingleServerIRCBot subclass
- Add nukes/globals.py with constants and exceptions
- Fix circular imports using TYPE_CHECKING guards
- Add 36 unit tests in tests/test_game.py
- Add requirements.txt and .gitignore

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Restore missing apocalypse continuation message in warhead.py
- Restore missing 'misses a turn' notification in warhead.py
- Improve test_missile_deploys_as_weapon with explicit weapon assertion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: giannitedesco <221359+giannitedesco@users.noreply.github.com>
Copilot AI changed the title [WIP] Modernize IRC bot code for Python 3.14 compatibility Modernize ircnukes: Python 3, typed, irc library, unit tests Feb 28, 2026
Comment thread nukes/globals.py Outdated
NUKE_YIELD_50MT: int = 50
NUKE_YIELD_75MT: int = 75
NUKE_YIELD_100MT: int = 100
NUKE_YIELD_200MT: int = 200

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use IntEnum or something

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, or solve this as a type where int(x) is self.yield and x.popdead is the death calculation, could be a NamedTuple perhaps

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d7c20ac. Added NukeYield(IntEnum) where int(NukeYield.MT100) == 100 and NukeYield.MT100.popdead == 25. The .popdead property uses a module-level lookup dict, so warhead.py's separate _BODYCOUNTS dict is gone. Old NUKE_YIELD_* aliases kept for compatibility.

Comment thread nukes/globals.py
"""Player is fully eliminated."""

CARD_STACK_LEN: int = 2
"""Number of cards in a player's queue."""

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use enums for these states

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d7c20ac. Added GameState(IntEnum) (INIT/PEACE/WAR/OVER) and PlayerState(IntEnum) (ALIVE/RETALIATE/DEAD). The old GAME_STATE_* / PLAYER_STATE_* names are kept as backward-compatible aliases pointing at the enum members.

Comment thread nukes/propaganda.py Outdated
from card import card
from globals import *

class propaganda(card):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use PEP8 style, eg. for classes, everything should pass flake8 / bandit etc..

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d7c20ac. All game classes renamed to PascalCase: Card, Deck, Player, Game, Warhead, Missile, Bomber, Propaganda, IrcNukes. flake8 --max-line-length=100 (with pep8-naming and flake8-bandit) now exits 0.

Comment thread nukes/warhead.py Outdated
NUKE_YIELD_50MT: 15,
NUKE_YIELD_75MT: 20,
NUKE_YIELD_100MT: 25,
NUKE_YIELD_200MT: 50,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bodycounts can probably be encoded in a yield enum

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d7c20ac. _BODYCOUNTS removed; Warhead.calc_fallout now uses self.megatons.popdead directly from the NukeYield enum.

Comment thread nukes/missile.py Outdated
from card import card
from globals import *

class missile(card):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use idiomatic type names

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d7c20acmissile is now Missile (along with all other game classes being renamed to PascalCase).

Copilot AI and others added 3 commits March 1, 2026 12:27
- Replace GAME_STATE_* and PLAYER_STATE_* int constants with GameState and
  PlayerState IntEnum classes in nukes/globals.py
- Replace NUKE_YIELD_* constants and _BODYCOUNTS dict with NukeYield(IntEnum)
  carrying a popdead property in nukes/globals.py
- Rename all game classes to PascalCase: card->Card, deck->Deck,
  player->Player, game->Game, warhead->Warhead, missile->Missile,
  bomber->Bomber, propaganda->Propaganda, ircnukes->IrcNukes
- Remove internal-invariant asserts; replace with proper exceptions
- Add # noqa: S311 to intentional random.randint calls
- Remove unused imports from game.py
- Keep backward-compat lowercase aliases in all modules and __init__.py
- Update nukebot.py, nukage.py, and tests/test_game.py to use new names

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…om deck

- Rename test_kill_no_game -> test_terminate_no_game to match method name
- Restore weapon: Any | None annotation for type clarity
- Add None guard in Game.__get_pop() to handle empty population deck

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add GameState(IntEnum), PlayerState(IntEnum) replacing int constants
- Add NukeYield(IntEnum) with .popdead property; remove _BODYCOUNTS dict
- Rename all game classes to PascalCase (Card, Deck, Player, Game,
  Warhead, Missile, Bomber, Propaganda, IrcNukes)
- Keep backward-compat lowercase aliases in each module and __init__
- Fix flake8: remove unused imports, replace asserts with proper
  exceptions, add noqa: S311 for intentional game randomness
- Fix game.__get_pop() return type annotation (int | None)
- Raise GameLogicError in calc_fallout when player has no game

Co-authored-by: giannitedesco <221359+giannitedesco@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants