Files
TheOnlyMace b4110c3d66
Some checks failed
CI / Bot (Python) (push) Failing after 49s
CI / Dashboard (Next.js) (push) Failing after 11s
Update project configuration files, and API routes accordingly. Add SQLite runtime file ignores to .gitignore for better file management.
2026-07-21 17:11:38 +02:00

219 lines
7.6 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ╔══════════════════════════════════════════════════════════════════╗
# ║ ║
# ║ +-+-+-+-+-+-+-+-+ ║
# ║ |H|e|x|a|H|o|s|t| ║
# ║ +-+-+-+-+-+-+-+-+ ║
# ║ ║
# ║ © 2026 HexaHost — All Rights Reserved ║
# ║ ║
# ║ discord ── https://discord.gg/hexahost ║
# ║ github ── https://github.com/theoneandonlymace ║
# ║ ║
# ╚══════════════════════════════════════════════════════════════════╝
from __future__ import annotations
from typing import Optional, Union
import asyncio
import discord
from discord.ext import commands
from .utils import DiscordColor, DEFAULT_COLOR
RED = "🔴"
BLUE = "🔵"
BLANK = ""
class ConnectFour:
"""
Connect-4 Game
"""
def __init__(self, *, red: discord.User, blue: discord.User) -> None:
self.red_player = red
self.blue_player = blue
self.board: list[list[str]] = [[BLANK for _ in range(7)] for _ in range(6)]
self._controls: tuple[str, ...] = (
"1",
"2",
"3",
"4",
"5",
"6",
"7",
)
self.turn = self.red_player
self.message: Optional[discord.Message] = None
self.winner: Optional[discord.User] = None
self._conversion: dict[str, int] = {
emoji: i for i, emoji in enumerate(self._controls)
}
self.player_to_emoji: dict[discord.User, str] = {
self.red_player: RED,
self.blue_player: BLUE,
}
self.emoji_to_player: dict[str, discord.User] = {
v: k for k, v in self.player_to_emoji.items()
}
def board_string(self) -> str:
board = "1⃣2⃣3⃣4⃣5⃣6⃣7\n"
for row in self.board:
board += "".join(row) + "\n"
return board
def make_embed(self, *, status: bool) -> discord.Embed:
embed = discord.Embed(color=discord.Color.random())
if not status:
embed.description = f"**Turn:** {self.turn.name}\n**Piece:** `{self.player_to_emoji[self.turn]}`"
else:
status_ = f"{self.winner} won!" if self.winner else "Tie"
embed.description = f"**Game over**\n{status_}"
return embed
def place_move(self, column: Union[str, int], user) -> list[list[str]]:
if isinstance(column, str):
if column not in self._controls:
raise KeyError("Provided emoji is not one of the valid controls")
column = self._conversion[column]
for x in range(5, -1, -1):
if self.board[x][column] == BLANK:
self.board[x][column] = self.player_to_emoji[user]
break
self.turn = self.red_player if user == self.blue_player else self.blue_player
return self.board
def is_game_over(self) -> bool:
if all(i != BLANK for i in self.board[0]):
return True
for x in range(6):
for i in range(4):
if (
self.board[x][i]
== self.board[x][i + 1]
== self.board[x][i + 2]
== self.board[x][i + 3]
and self.board[x][i] != BLANK
):
self.winner = self.emoji_to_player[self.board[x][i]]
return True
for x in range(3):
for i in range(7):
if (
self.board[x][i]
== self.board[x + 1][i]
== self.board[x + 2][i]
== self.board[x + 3][i]
and self.board[x][i] != BLANK
):
self.winner = self.emoji_to_player[self.board[x][i]]
return True
for x in range(3):
for i in range(4):
if (
self.board[x][i]
== self.board[x + 1][i + 1]
== self.board[x + 2][i + 2]
== self.board[x + 3][i + 3]
and self.board[x][i] != BLANK
):
self.winner = self.emoji_to_player[self.board[x][i]]
return True
for x in range(5, 2, -1):
for i in range(4):
if (
self.board[x][i]
== self.board[x - 1][i + 1]
== self.board[x - 2][i + 2]
== self.board[x - 3][i + 3]
and self.board[x][i] != BLANK
):
self.winner = self.emoji_to_player[self.board[x][i]]
return True
return False
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
timeout: Optional[float] = None,
embed_color: DiscordColor = discord.Color.random(),
remove_reaction_after: bool = False,
**kwargs,
) -> discord.Message:
"""
starts the Connect-4 game
Parameters
----------
ctx : commands.Context
the context of the invokation command
timeout : Optional[float], optional
the timeout for when waiting, by default None
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
remove_reaction_after : bool, optional
specifies whether or not to remove the user's move reaction, by default False
Returns
-------
discord.Message
returns the game message
"""
self.embed_color = discord.Color.random()
embed = self.make_embed(status=False)
self.message = await ctx.send(self.board_string(), embed=embed, **kwargs)
for button in self._controls:
await self.message.add_reaction(button)
while not ctx.bot.is_closed():
def check(reaction: discord.Reaction, user: discord.User) -> bool:
return (
str(reaction.emoji) in self._controls
and user == self.turn
and reaction.message == self.message
and self.board[0][self._conversion[str(reaction.emoji)]] == BLANK
)
try:
reaction, user = await ctx.bot.wait_for(
"reaction_add", timeout=timeout, check=check
)
except asyncio.TimeoutError:
break
emoji = str(reaction.emoji)
self.place_move(emoji, user)
if status := self.is_game_over():
break
if remove_reaction_after:
await self.message.remove_reaction(emoji, user)
embed = self.make_embed(status=False)
await self.message.edit(content=self.board_string(), embed=embed)
embed = self.make_embed(status=status)
await self.message.edit(content=self.board_string(), embed=embed)
return self.message