Files
Mace-AIO-Discord-Bot---With…/bot/games/connect_four.py
2026-07-16 11:14:48 +05:30

220 lines
7.9 KiB
Python
Raw 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.
# ╔══════════════════════════════════════════════════════════════════╗
# ║ ║
# ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
# ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
# ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
# ║ ║
# ║ © 2026 CodeX Devs — All Rights Reserved ║
# ║ ║
# ║ discord ── https://discord.gg/codexdev ║
# ║ youtube ── https://youtube.com/@CodeXDevs ║
# ║ github ── https://github.com/RayExo ║
# ║ ║
# ╚══════════════════════════════════════════════════════════════════╝
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