From 29efddbb51a461098c7617ad15aa872d4ec4043f Mon Sep 17 00:00:00 2001 From: w Date: Wed, 2 Jul 2025 23:41:17 -0300 Subject: [PATCH 01/14] Add duel table migration for game mechanics --- migrations/0007_duelv1.sql | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 migrations/0007_duelv1.sql diff --git a/migrations/0007_duelv1.sql b/migrations/0007_duelv1.sql new file mode 100644 index 0000000..16b5e7d --- /dev/null +++ b/migrations/0007_duelv1.sql @@ -0,0 +1,33 @@ +/* +Kemoverse - a gacha-style bot for the Fediverse. +Copyright © 2025 Waifu + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see https://www.gnu.org/licenses/. +*/ +CREATE TABLE duel ( + duel_id INTEGER PRIMARY KEY AUTOINCREMENT, + player_1_id INTEGER NOT NULL, + player_2_id INTEGER NOT NULL, + attacker_id INTEGER, + player_1_cards TEXT NOT NULL DEFAULT '[]', + player_2_cards TEXT NOT NULL DEFAULT '[]', + graveyard_p1 TEXT NOT NULL DEFAULT '[]', + graveyard_p2 TEXT NOT NULL DEFAULT '[]', + round INTEGER NOT NULL DEFAULT 1, + competitive BOOLEAN NOT NULL DEFAULT 0, + last_round_dt TEXT, + is_finished BOOLEAN NOT NULL DEFAULT 0, + points INTEGER NOT NULL DEFAULT 0, + winner_id INTEGER +); -- 2.39.2 From e5c7196fa9861ff09d57cd94d32b0e06a6ed432a Mon Sep 17 00:00:00 2001 From: w Date: Fri, 4 Jul 2025 00:01:55 -0300 Subject: [PATCH 02/14] Add duel request functionality and update duel table schema --- bot/response.py | 30 ++++++++++++++++++++++++++++++ migrations/0007_duelv1.sql | 3 ++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/bot/response.py b/bot/response.py index 8ec698e..5c7f35f 100644 --- a/bot/response.py +++ b/bot/response.py @@ -181,6 +181,31 @@ def do_help(author: str) -> BotResponse: 'attachment_urls': None } +def duel_request(author: str, args: list[str]) -> BotResponse: + '''Sends a duel request to another user.''' + if len(args) == 0: + return { + 'message': f'{author} Please specify a user to duel.', + 'attachment_urls': None + } + + target = args[0] + if not db.get_player(target): + return { + 'message': f'{author} User {target} does not exist or \ + has not signed up, please sign up before challenging to a duel.', + 'attachment_urls': None + } + duel_type = 'casual' + if len(args) == 1 and args[0] == "competitive": + duel_type = 'competitive' + + db.insert_duel_request(author, target, duel_type) + return { + 'message': f'{target} You have been challenged to a {duel_type} duel by \ + {author}! Reply with `accept_duel` to accept the challenge.', + 'attachment_urls': None + } def delete_account(author: str) -> BotResponse: return { @@ -325,6 +350,11 @@ def generate_response(notification: ParsedNotification) -> BotResponse | None: res = delete_account(author) case 'confirm_delete_account': res = confirm_delete(author) + case 'duel_request': + res = duel_request( + author, + notification['arguments'] + ) case _: pass diff --git a/migrations/0007_duelv1.sql b/migrations/0007_duelv1.sql index 16b5e7d..34e6918 100644 --- a/migrations/0007_duelv1.sql +++ b/migrations/0007_duelv1.sql @@ -29,5 +29,6 @@ CREATE TABLE duel ( last_round_dt TEXT, is_finished BOOLEAN NOT NULL DEFAULT 0, points INTEGER NOT NULL DEFAULT 0, - winner_id INTEGER + winner_id INTEGER, + accepted BOOLEAN NOT NULL DEFAULT 0 ); -- 2.39.2 From 5d150bfe23991d967ab719273955494714460a68 Mon Sep 17 00:00:00 2001 From: w Date: Fri, 4 Jul 2025 00:06:25 -0300 Subject: [PATCH 03/14] typo --- migrations/0007_duelv1.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/0007_duelv1.sql b/migrations/0007_duelv1.sql index 34e6918..9a1c8e7 100644 --- a/migrations/0007_duelv1.sql +++ b/migrations/0007_duelv1.sql @@ -15,7 +15,7 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/. */ -CREATE TABLE duel ( +CREATE TABLE duels ( duel_id INTEGER PRIMARY KEY AUTOINCREMENT, player_1_id INTEGER NOT NULL, player_2_id INTEGER NOT NULL, -- 2.39.2 From fc335d45378d5f5758770837750c1c9325cf3fb1 Mon Sep 17 00:00:00 2001 From: w Date: Fri, 4 Jul 2025 00:24:34 -0300 Subject: [PATCH 04/14] Add duel request functionality and update duel table schema --- bot/db_utils.py | 23 +++++++++++++++++++++++ bot/response.py | 6 ++++++ migrations/0007_duelv1.sql | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/bot/db_utils.py b/bot/db_utils.py index 45cac82..491c1ec 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -18,6 +18,7 @@ from random import choices import sqlite3 import config from custom_types import Card +from datetime import datetime DB_PATH = config.DB_PATH CONNECTION: sqlite3.Connection @@ -100,6 +101,28 @@ def get_player(username: str) -> int: return int(player[0]) return 0 +def insert_duel_request(username: str, target: str, duel_type: str) -> None: + '''Inserts a duel request into the database. + + Args: + username (str): The username of the player who initiated the duel. + target (str): The username of the player being challenged. + duel_type (str): The type of duel (e.g., casual, competitive). + + + ''' + player_1_1d = get_player(username) + player_2_id = get_player(target) + + # picks a random attacker + attacker_id = choices([player_1_1d, player_2_id], k=1)[0] + + # sets the last round date to the current time + last_round_dt = datetime.now().isoformat() + + CURSOR.execute( + 'INSERT INTO duels (player_1_id, player_2_id, attacker_id,last_round_dt) VALUES (?, ?, ?, ?)', + ) def insert_player(username: str) -> int: '''Insert a new player with default has_rolled = False and return their diff --git a/bot/response.py b/bot/response.py index 5c7f35f..0b3521b 100644 --- a/bot/response.py +++ b/bot/response.py @@ -196,6 +196,12 @@ def duel_request(author: str, args: list[str]) -> BotResponse: has not signed up, please sign up before challenging to a duel.', 'attachment_urls': None } + if target == author: + return { + 'message': f'{author} You can\'t duel yourself! \ + Try challenging someone else.', + 'attachment_urls': None + } duel_type = 'casual' if len(args) == 1 and args[0] == "competitive": duel_type = 'competitive' diff --git a/migrations/0007_duelv1.sql b/migrations/0007_duelv1.sql index 9a1c8e7..4ed9eb0 100644 --- a/migrations/0007_duelv1.sql +++ b/migrations/0007_duelv1.sql @@ -29,6 +29,6 @@ CREATE TABLE duels ( last_round_dt TEXT, is_finished BOOLEAN NOT NULL DEFAULT 0, points INTEGER NOT NULL DEFAULT 0, - winner_id INTEGER, + winner_id INTEGER DEFAULT NULL, accepted BOOLEAN NOT NULL DEFAULT 0 ); -- 2.39.2 From 03f6f1b80c5133cd5962db20a6f230c061e2f4e3 Mon Sep 17 00:00:00 2001 From: w Date: Sun, 6 Jul 2025 15:17:47 -0300 Subject: [PATCH 05/14] comment --- bot/response.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bot/response.py b/bot/response.py index 0b3521b..bdcbd5b 100644 --- a/bot/response.py +++ b/bot/response.py @@ -181,6 +181,8 @@ def do_help(author: str) -> BotResponse: 'attachment_urls': None } +# Dueling + def duel_request(author: str, args: list[str]) -> BotResponse: '''Sends a duel request to another user.''' if len(args) == 0: @@ -207,12 +209,15 @@ def duel_request(author: str, args: list[str]) -> BotResponse: duel_type = 'competitive' db.insert_duel_request(author, target, duel_type) + return { 'message': f'{target} You have been challenged to a {duel_type} duel by \ {author}! Reply with `accept_duel` to accept the challenge.', 'attachment_urls': None } + + def delete_account(author: str) -> BotResponse: return { 'message': f'{author} ⚠️ This will permanently delete your account \ -- 2.39.2 From ffa298631abc0ed0413c27853e50177778177f12 Mon Sep 17 00:00:00 2001 From: w Date: Sun, 6 Jul 2025 15:18:48 -0300 Subject: [PATCH 06/14] Update duel request functionality and schema to include competitive flag and creation date --- bot/db_utils.py | 12 +++++++----- migrations/0007_duelv1.sql | 12 ++++++++++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/bot/db_utils.py b/bot/db_utils.py index 491c1ec..a61df88 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -111,17 +111,19 @@ def insert_duel_request(username: str, target: str, duel_type: str) -> None: ''' + + # get the player ids player_1_1d = get_player(username) player_2_id = get_player(target) - # picks a random attacker - attacker_id = choices([player_1_1d, player_2_id], k=1)[0] + # sets the creation date + created_at = datetime.now().isoformat() - # sets the last round date to the current time - last_round_dt = datetime.now().isoformat() + competitive = duel_type + # insert the duel request CURSOR.execute( - 'INSERT INTO duels (player_1_id, player_2_id, attacker_id,last_round_dt) VALUES (?, ?, ?, ?)', + 'INSERT INTO duels (player_1_id, player_2_id, attacker_id,last_round_dt,competitive) VALUES (?, ?, ?, ?)', ) def insert_player(username: str) -> int: diff --git a/migrations/0007_duelv1.sql b/migrations/0007_duelv1.sql index 4ed9eb0..be62148 100644 --- a/migrations/0007_duelv1.sql +++ b/migrations/0007_duelv1.sql @@ -29,6 +29,14 @@ CREATE TABLE duels ( last_round_dt TEXT, is_finished BOOLEAN NOT NULL DEFAULT 0, points INTEGER NOT NULL DEFAULT 0, - winner_id INTEGER DEFAULT NULL, - accepted BOOLEAN NOT NULL DEFAULT 0 + winner_id INTEGER DEFAULT NULL +); + +CREATE TABLE duel_requests ( + duel_request_id INTEGER PRIMARY KEY AUTOINCREMENT, + player_1_id INTEGER NOT NULL, + player_2_id INTEGER NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + accepted BOOLEAN NOT NULL DEFAULT 0, + competitive BOOLEAN NOT NULL DEFAULT 0 ); -- 2.39.2 From 528e21db23216f713f9f4fb395a272b5395bcba1 Mon Sep 17 00:00:00 2001 From: w Date: Sun, 6 Jul 2025 16:06:55 -0300 Subject: [PATCH 07/14] Refactor duel request handling and add accept duel functionality --- bot/db_utils.py | 5 ++--- bot/response.py | 27 ++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/bot/db_utils.py b/bot/db_utils.py index a61df88..5ba7ee0 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -116,14 +116,13 @@ def insert_duel_request(username: str, target: str, duel_type: str) -> None: player_1_1d = get_player(username) player_2_id = get_player(target) - # sets the creation date - created_at = datetime.now().isoformat() competitive = duel_type # insert the duel request CURSOR.execute( - 'INSERT INTO duels (player_1_id, player_2_id, attacker_id,last_round_dt,competitive) VALUES (?, ?, ?, ?)', + 'INSERT INTO duel_requests (player_1_id, player_2_id, competitive) VALUES (?, ?, ?)', + (player_1_1d, player_2_id, competitive) ) def insert_player(username: str) -> int: diff --git a/bot/response.py b/bot/response.py index bdcbd5b..64053b8 100644 --- a/bot/response.py +++ b/bot/response.py @@ -209,14 +209,39 @@ def duel_request(author: str, args: list[str]) -> BotResponse: duel_type = 'competitive' db.insert_duel_request(author, target, duel_type) - + return { 'message': f'{target} You have been challenged to a {duel_type} duel by \ {author}! Reply with `accept_duel` to accept the challenge.', 'attachment_urls': None } +def accept_duel(author: str, args: list[str]) -> BotResponse: + '''Accepts a duel request from another user.''' + if len(args) == 0: + return { + 'message': f'{author} Please specify a user to accept the duel from.', + 'attachment_urls': None + } + target = args[0] + if not db.get_player(target): + return { + 'message': f'{author} User {target} does not exist or \ + has not signed up, please sign up before accepting a duel.', + 'attachment_urls': None + } + + if db.accept_duel_request(author, target): + return { + 'message': f'{author} You have accepted the duel request from {target}.', + 'attachment_urls': None + } + else: + return { + 'message': f'{author} No duel request found from {target}.', + 'attachment_urls': None + } def delete_account(author: str) -> BotResponse: return { -- 2.39.2 From 9bcbfca5df75e9c65313ca2f23e4b43567078329 Mon Sep 17 00:00:00 2001 From: w Date: Sun, 6 Jul 2025 16:46:17 -0300 Subject: [PATCH 08/14] Update insert_duel_request to return duel ID and modify duel_request response message --- bot/db_utils.py | 7 +++++-- bot/response.py | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/bot/db_utils.py b/bot/db_utils.py index 5ba7ee0..5591577 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -101,7 +101,7 @@ def get_player(username: str) -> int: return int(player[0]) return 0 -def insert_duel_request(username: str, target: str, duel_type: str) -> None: +def insert_duel_request(username: str, target: str, duel_type: str) -> int: '''Inserts a duel request into the database. Args: @@ -119,11 +119,14 @@ def insert_duel_request(username: str, target: str, duel_type: str) -> None: competitive = duel_type - # insert the duel request + # insert the duel request and get the duel ID CURSOR.execute( 'INSERT INTO duel_requests (player_1_id, player_2_id, competitive) VALUES (?, ?, ?)', (player_1_1d, player_2_id, competitive) ) + duel_id = CURSOR.lastrowid + + return duel_id def insert_player(username: str) -> int: '''Insert a new player with default has_rolled = False and return their diff --git a/bot/response.py b/bot/response.py index 64053b8..b2d10f0 100644 --- a/bot/response.py +++ b/bot/response.py @@ -208,11 +208,11 @@ def duel_request(author: str, args: list[str]) -> BotResponse: if len(args) == 1 and args[0] == "competitive": duel_type = 'competitive' - db.insert_duel_request(author, target, duel_type) + duel_id = db.insert_duel_request(author, target, duel_type) return { - 'message': f'{target} You have been challenged to a {duel_type} duel by \ - {author}! Reply with `accept_duel` to accept the challenge.', + 'message': f'{target} You have been challenged to a {duel_type} duel by {author}! \ + Reply with `accept_duel {duel_id}` to accept the challenge. Duel ID:{duel_id}.', 'attachment_urls': None } -- 2.39.2 From b8bf9449baf81038dbd03f09b6080c2a1bac60db Mon Sep 17 00:00:00 2001 From: w Date: Sun, 6 Jul 2025 22:41:07 -0300 Subject: [PATCH 09/14] Add duel request retrieval and acceptance functionality --- bot/db_utils.py | 103 +++++++++++++++++++++++++++++++++++++++++++++++- bot/response.py | 45 +++++++++++++-------- 2 files changed, 131 insertions(+), 17 deletions(-) diff --git a/bot/db_utils.py b/bot/db_utils.py index 5591577..8de4b49 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -125,9 +125,110 @@ def insert_duel_request(username: str, target: str, duel_type: str) -> int: (player_1_1d, player_2_id, competitive) ) duel_id = CURSOR.lastrowid - + return duel_id +def get_duel_request(duel_request_id: int) -> dict | None: + '''Retrieves a duel request by its ID. + + Args: + duel_id (int): The ID of the duel request. + + Returns: + dict: A dictionary containing the duel request details, or None if not found. + ''' + CURSOR.execute( + 'SELECT * FROM duel_requests WHERE duel_request_id = ?', + (duel_request_id,) + ) + row = CURSOR.fetchone() + if row: + return dict(row) + return None + +def accept_duel_request(duel_request_id: int) -> bool: + ''' Sets up the duel request to an actual duel. + Args: + duel_request_id (int): The ID of the duel request to begin. + ''' + create_duel(duel_request_id) + + CURSOR.execute( + 'UPDATE duel_requests SET accepted = 1 WHERE duel_request_id = ?', + (duel_request_id,) + ) + +def take_cards(player_id: int, count: int) -> list[int]: + '''Takes a specified number of random cards from a player. + + Args: + player_id (int): The ID of the player to take cards from. + count (int): The number of cards to take. + + Returns: + list[int]: A list of card IDs taken from the player. + ''' + CURSOR.execute( + 'SELECT id FROM pulls WHERE player_id = ? ORDER BY RANDOM() LIMIT ?', + (player_id, count) + ) + rows = CURSOR.fetchall() + return [row['id'] for row in rows] + +def create_duel(duel_request_id: int) -> None: + '''Creates a duel from a duel request. + + Args: + duel_request_id (int): The ID of the duel request to create a duel from. + ''' + CURSOR.execute( + 'SELECT player_1_id, player_2_id, competitive FROM duel_requests WHERE duel_request_id = ?', + (duel_request_id,) + ) + row = CURSOR.fetchone() + + # Select a random player to set as attacker + attacker_id = choices([row['player_1_id'], row['player_2_id']])[0] + + # Set the last round date to now + last_round_dt = datetime.now().isoformat() + + # take 3 random cards from each player + + player_1_cards = take_cards(row['player_1_id'], 3) + player_2_cards = take_cards(row['player_2_id'], 3) + if not player_1_cards or not player_2_cards: + raise ValueError("One of the players has no cards to duel with.") + + # Insert the duel into the database, only players ids, attacker id, cards, last round date and competitive flag + + CURSOR.execute( + ''' + INSERT INTO duels (player_1_id, player_2_id, attacker_id, player_1_cards, player_2_cards, last_round_dt, is_finished, competitive) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''', + ( + row['player_1_id'], + row['player_2_id'], + attacker_id, + ','.join(map(str, player_1_cards)), + ','.join(map(str, player_2_cards)), + last_round_dt, + False, + row['competitive'] + ) + ) + duel_id = CURSOR.lastrowid + if duel_id: + # Start duel + #start_duel(duel_id) + pass + + + + +##def start_duel(duel_id: int) -> None: + def insert_player(username: str) -> int: '''Insert a new player with default has_rolled = False and return their player ID.''' diff --git a/bot/response.py b/bot/response.py index b2d10f0..96fe385 100644 --- a/bot/response.py +++ b/bot/response.py @@ -217,31 +217,44 @@ def duel_request(author: str, args: list[str]) -> BotResponse: } def accept_duel(author: str, args: list[str]) -> BotResponse: - '''Accepts a duel request from another user.''' + '''Accepts a duel request with an id.''' if len(args) == 0: return { - 'message': f'{author} Please specify a user to accept the duel from.', + 'message': f'{author} Please specify a duel ID.', 'attachment_urls': None } - target = args[0] - if not db.get_player(target): + duel_request_id = args[0] + duel_request = db.get_duel_request(duel_request_id) + if not duel_request: return { - 'message': f'{author} User {target} does not exist or \ - has not signed up, please sign up before accepting a duel.', + 'message': f'{author} Duel request with ID {duel_request_id} does not exist.', 'attachment_urls': None } + + # Check if the author is the target of the duel request + if duel_request['player_2_id'] != db.get_player(author): + return { + 'message': f'{author} You cannot accept this duel request. It is not \ + addressed to you.', + 'attachment_urls': None + } + # Check if the duel was already accepted + if duel_request['accepted']: + return { + 'message': f'{author} This duel request has already been accepted.', + 'attachment_urls': None + } + # Accept the duel request + db.accept_duel_request(duel_request_id) + return { + 'message': f'{author} You have accepted the duel request with ID {duel_request_id}. \ + The duel will shortly begin.', + 'attachment_urls': None + } - if db.accept_duel_request(author, target): - return { - 'message': f'{author} You have accepted the duel request from {target}.', - 'attachment_urls': None - } - else: - return { - 'message': f'{author} No duel request found from {target}.', - 'attachment_urls': None - } + + def delete_account(author: str) -> BotResponse: return { -- 2.39.2 From 59f88b8a4a36e3397948ed3b5020824d496a2a0c Mon Sep 17 00:00:00 2001 From: w Date: Sun, 6 Jul 2025 23:39:51 -0300 Subject: [PATCH 10/14] Enhance duel creation and response handling: add duel notification and card check --- bot/db_utils.py | 29 +++++++++++++++++++++++++---- bot/response.py | 9 +++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/bot/db_utils.py b/bot/db_utils.py index 8de4b49..3667a93 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -197,8 +197,6 @@ def create_duel(duel_request_id: int) -> None: player_1_cards = take_cards(row['player_1_id'], 3) player_2_cards = take_cards(row['player_2_id'], 3) - if not player_1_cards or not player_2_cards: - raise ValueError("One of the players has no cards to duel with.") # Insert the duel into the database, only players ids, attacker id, cards, last round date and competitive flag @@ -221,8 +219,31 @@ def create_duel(duel_request_id: int) -> None: duel_id = CURSOR.lastrowid if duel_id: # Start duel - #start_duel(duel_id) - pass + # Initialize the duel by sending a notification to the players + from fediverse_factory import get_fediverse_service + from fediverse_types import Visibility + + fediverse_service = get_fediverse_service(config.INSTANCE_TYPE) + + + if row['competitive']==1: + duel_type = 'Competitive' + else: + duel_type = 'Casual' + + player_1_name = CURSOR.execute( + 'SELECT username FROM players WHERE id = ?', + (row['player_1_id'],) + ).fetchone()['username'] + player_2_name = CURSOR.execute( + 'SELECT username FROM players WHERE id = ?', + (row['player_2_id'],) + ).fetchone()['username'] + + fediverse_service.create_post( + text=f'⚔️ A {duel_type} Duel started between {player_1_name} and {player_2_name}!', + visibility=Visibility.HOME + ) diff --git a/bot/response.py b/bot/response.py index 96fe385..8db3595 100644 --- a/bot/response.py +++ b/bot/response.py @@ -204,6 +204,10 @@ def duel_request(author: str, args: list[str]) -> BotResponse: Try challenging someone else.', 'attachment_urls': None } + + # Check if both the users have enough cards to duel + # TODO + duel_type = 'casual' if len(args) == 1 and args[0] == "competitive": duel_type = 'competitive' @@ -404,6 +408,11 @@ def generate_response(notification: ParsedNotification) -> BotResponse | None: author, notification['arguments'] ) + case 'accept_duel': + res = accept_duel( + author, + notification['arguments'] + ) case _: pass -- 2.39.2 From 4a0318ec103f88a46906d064e9dd0b6207394009 Mon Sep 17 00:00:00 2001 From: w Date: Mon, 7 Jul 2025 00:49:59 -0300 Subject: [PATCH 11/14] Refactor duel creation: enhance start_duel function and add thread ID to duels table --- bot/db_utils.py | 96 +++++++++++++++++++++++++++----------- migrations/0007_duelv1.sql | 1 + 2 files changed, 71 insertions(+), 26 deletions(-) diff --git a/bot/db_utils.py b/bot/db_utils.py index 3667a93..16e9610 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -78,7 +78,7 @@ def get_random_card() -> Card | None: def get_cards(card_ids: list[int]) -> list[tuple]: ''' - Retrieves stats for a list of card IDs. + Retrieves information about cards from the database by their IDs. Returns a list of tuples: (id, name, rarity, file_id, power, charm, wit, ...) ''' if not card_ids: @@ -219,36 +219,80 @@ def create_duel(duel_request_id: int) -> None: duel_id = CURSOR.lastrowid if duel_id: # Start duel - # Initialize the duel by sending a notification to the players - from fediverse_factory import get_fediverse_service - from fediverse_types import Visibility - - fediverse_service = get_fediverse_service(config.INSTANCE_TYPE) + start_duel(duel_id, row, player_1_cards, player_2_cards) - - if row['competitive']==1: - duel_type = 'Competitive' - else: - duel_type = 'Casual' - - player_1_name = CURSOR.execute( - 'SELECT username FROM players WHERE id = ?', - (row['player_1_id'],) - ).fetchone()['username'] - player_2_name = CURSOR.execute( - 'SELECT username FROM players WHERE id = ?', - (row['player_2_id'],) - ).fetchone()['username'] - - fediverse_service.create_post( - text=f'⚔️ A {duel_type} Duel started between {player_1_name} and {player_2_name}!', - visibility=Visibility.HOME - ) -##def start_duel(duel_id: int) -> None: +def start_duel(duel_id: int, row: dict, player_1_cards: list[int], player_2_cards: list[int]) -> None: + '''Starts a duel by sending a notification to the players and sending the cards. + Args: + duel_id (int): The ID of the duel. + row (dict): The row containing duel request details. + player_1_cards (list[int]): List of card IDs for player 1. + player_2_cards (list[int]): List of card IDs for player 2. + ''' + # Initialize the duel thread by sending a notification to the players + from fediverse_factory import get_fediverse_service + from fediverse_types import Visibility + + fediverse_service = get_fediverse_service(config.INSTANCE_TYPE) + + + if row['competitive']==1: + duel_type = 'Competitive' + else: + duel_type = 'Casual' + + player_1_name = CURSOR.execute( + 'SELECT username FROM players WHERE id = ?', + (row['player_1_id'],) + ).fetchone()['username'] + player_2_name = CURSOR.execute( + 'SELECT username FROM players WHERE id = ?', + (row['player_2_id'],) + ).fetchone()['username'] + + thread_id = fediverse_service.create_post( + text=f'⚔️ A {duel_type} Duel started between {player_1_name} and {player_2_name}! ROUND 1 has begun!', + visibility=Visibility.HOME + ) + # Update the duel with the thread ID + CURSOR.execute( + 'UPDATE duels SET thread_id = ? WHERE id = ?', + (thread_id, duel_id) + ) + + + # Send the cards to the players + # Player 1 + # Get the card details for player 1 + player_1_cards_details = get_cards(player_1_cards) + # Send a notification to player 1 with their cards + fediverse_service.create_post( + text=f'{player_1_name}Your cards for the duel. \ + Reply with select <1,2 or 3> to select a card for the duel.', + visibility=Visibility.SPECIFIED, + file_ids=[card['file_id'] for card in player_1_cards_details], + #visible_user_ids=player_1_name + ) + + # Player 2 + # Get the card details for player 2 + player_2_cards_details = get_cards(player_2_cards) + # Send a notification to player 2 with their cards + fediverse_service.create_post( + text=f'{player_2_name}Your cards for the duel. \ + Reply with select <1,2 or 3> to select a card for the duel.', + visibility=Visibility.SPECIFIED, + file_ids=[card['file_id'] for card in player_2_cards_details], + #visible_user_ids=player_2_name + ) + + + + def insert_player(username: str) -> int: '''Insert a new player with default has_rolled = False and return their diff --git a/migrations/0007_duelv1.sql b/migrations/0007_duelv1.sql index be62148..a55d757 100644 --- a/migrations/0007_duelv1.sql +++ b/migrations/0007_duelv1.sql @@ -29,6 +29,7 @@ CREATE TABLE duels ( last_round_dt TEXT, is_finished BOOLEAN NOT NULL DEFAULT 0, points INTEGER NOT NULL DEFAULT 0, + thread_id TEXT DEFAULT NULL, winner_id INTEGER DEFAULT NULL ); -- 2.39.2 From f686daaeae9d4b6b8fda411784067787f48e720d Mon Sep 17 00:00:00 2001 From: w Date: Tue, 8 Jul 2025 00:21:17 -0300 Subject: [PATCH 12/14] add to the player table the fedi server id --- migrations/0007_duelv1.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/migrations/0007_duelv1.sql b/migrations/0007_duelv1.sql index a55d757..c0870cf 100644 --- a/migrations/0007_duelv1.sql +++ b/migrations/0007_duelv1.sql @@ -41,3 +41,5 @@ CREATE TABLE duel_requests ( accepted BOOLEAN NOT NULL DEFAULT 0, competitive BOOLEAN NOT NULL DEFAULT 0 ); + +ALTER TABLE players ADD COLUMN server_id text NOT NULL DEFAULT '0'; \ No newline at end of file -- 2.39.2 From c8bc16638e249b3f5cafa26b7c0f01cb93ac91dc Mon Sep 17 00:00:00 2001 From: w Date: Tue, 8 Jul 2025 23:30:30 -0300 Subject: [PATCH 13/14] Fix card retrieval and enhance duel notifications: update SQL queries and include server IDs for players --- bot/db_utils.py | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/bot/db_utils.py b/bot/db_utils.py index 16e9610..b911b9b 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -88,7 +88,9 @@ def get_cards(card_ids: list[int]) -> list[tuple]: query = f'SELECT * FROM cards WHERE id IN ({placeholders})' CURSOR.execute(query, card_ids) - return CURSOR.fetchall() + + res = CURSOR.fetchall() + return res def get_player(username: str) -> int: '''Retrieve a player ID by username, or return None if not found.''' @@ -169,11 +171,11 @@ def take_cards(player_id: int, count: int) -> list[int]: list[int]: A list of card IDs taken from the player. ''' CURSOR.execute( - 'SELECT id FROM pulls WHERE player_id = ? ORDER BY RANDOM() LIMIT ?', + 'SELECT card_id FROM pulls WHERE player_id = ? ORDER BY RANDOM() LIMIT ?', (player_id, count) ) rows = CURSOR.fetchall() - return [row['id'] for row in rows] + return [row['card_id'] for row in rows] def create_duel(duel_request_id: int) -> None: '''Creates a duel from a duel request. @@ -245,37 +247,46 @@ def start_duel(duel_id: int, row: dict, player_1_cards: list[int], player_2_card else: duel_type = 'Casual' - player_1_name = CURSOR.execute( - 'SELECT username FROM players WHERE id = ?', + # Get both the player 1 name and the player 1 server_id + player_1 = CURSOR.execute( + 'SELECT username, server_id FROM players WHERE id = ?', (row['player_1_id'],) - ).fetchone()['username'] - player_2_name = CURSOR.execute( - 'SELECT username FROM players WHERE id = ?', - (row['player_2_id'],) - ).fetchone()['username'] + ).fetchone() + player_1_name = player_1[0] + player_1_server_id = player_1[1] + # Get both the player 2 name and the player 2 server_id + player_2_name = CURSOR.execute( + 'SELECT username, server_id FROM players WHERE id = ?', + (row['player_2_id'],) + ).fetchone() + player_2_name = player_2_name[0] + player_2_server_id = player_2_name[1] + + # Create a thread for the duel thread_id = fediverse_service.create_post( text=f'⚔️ A {duel_type} Duel started between {player_1_name} and {player_2_name}! ROUND 1 has begun!', visibility=Visibility.HOME ) # Update the duel with the thread ID - CURSOR.execute( + """CURSOR.execute( 'UPDATE duels SET thread_id = ? WHERE id = ?', (thread_id, duel_id) - ) + )""" - + print(player_1_cards) # Send the cards to the players # Player 1 # Get the card details for player 1 player_1_cards_details = get_cards(player_1_cards) + print(player_1_cards_details) # Send a notification to player 1 with their cards fediverse_service.create_post( - text=f'{player_1_name}Your cards for the duel. \ + text=f'{player_1_name} Your cards for the duel. \ Reply with select <1,2 or 3> to select a card for the duel.', visibility=Visibility.SPECIFIED, file_ids=[card['file_id'] for card in player_1_cards_details], - #visible_user_ids=player_1_name + visible_user_ids=[player_1_server_id] ) # Player 2 @@ -283,11 +294,11 @@ def start_duel(duel_id: int, row: dict, player_1_cards: list[int], player_2_card player_2_cards_details = get_cards(player_2_cards) # Send a notification to player 2 with their cards fediverse_service.create_post( - text=f'{player_2_name}Your cards for the duel. \ + text=f'{player_2_name} Your cards for the duel. \ Reply with select <1,2 or 3> to select a card for the duel.', visibility=Visibility.SPECIFIED, file_ids=[card['file_id'] for card in player_2_cards_details], - #visible_user_ids=player_2_name + visible_user_ids=[player_2_server_id] ) -- 2.39.2 From 1a1964f956952796ddaebbf7aac231118bb1923a Mon Sep 17 00:00:00 2001 From: w Date: Wed, 9 Jul 2025 09:58:12 -0300 Subject: [PATCH 14/14] Add duel update functionality: implement card and stat selection for duels --- bot/db_utils.py | 4 ++-- bot/response.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/bot/db_utils.py b/bot/db_utils.py index b911b9b..c225e02 100644 --- a/bot/db_utils.py +++ b/bot/db_utils.py @@ -294,8 +294,8 @@ def start_duel(duel_id: int, row: dict, player_1_cards: list[int], player_2_card player_2_cards_details = get_cards(player_2_cards) # Send a notification to player 2 with their cards fediverse_service.create_post( - text=f'{player_2_name} Your cards for the duel. \ - Reply with select <1,2 or 3> to select a card for the duel.', + text=f'{player_2_name} Your cards for the duel. You are defending!. \ + Select a card with <1,2 or 3> and a stat with <1,2 or 3> for Power, Charm and Wit. Example: select 2 3 would select the second card and use its Wit stat.', visibility=Visibility.SPECIFIED, file_ids=[card['file_id'] for card in player_2_cards_details], visible_user_ids=[player_2_server_id] diff --git a/bot/response.py b/bot/response.py index 8db3595..9be0a56 100644 --- a/bot/response.py +++ b/bot/response.py @@ -257,8 +257,53 @@ def accept_duel(author: str, args: list[str]) -> BotResponse: 'attachment_urls': None } +def update_duel(author: str, args: list[str]) -> BotResponse: + '''Updates the duel with the selected card and stat.''' + if len(args) != 2: + return { + 'message': f'{author} Please specify a card number and a stat number.', + 'attachment_urls': None + } - + if not all(is_float(arg) for arg in args): + return { + 'message': f'{author} Invalid arguments: both card number and stat number must be numbers.', + 'attachment_urls': None + } + + card_number = int(args[0]) - 1 # Convert to zero-based index + stat_number = int(args[1]) - 1 # Convert to zero-based index + + duel = db.get_duel_by_player(author) + if not duel: + return { + 'message': f'{author} You are not currently in a duel.', + 'attachment_urls': None + } + + player_id = db.get_player(author) + player_cards = db.get_player_cards(player_id) + + if card_number < 0 or card_number >= len(player_cards): + return { + 'message': f'{author} Invalid card number: {card_number + 1}.', + 'attachment_urls': None + } + + selected_card = player_cards[card_number] + if stat_number < 0 or stat_number >= len(selected_card['stats']): + return { + 'message': f'{author} Invalid stat number: {stat_number + 1}.', + 'attachment_urls': None + } + + db.update_duel_selection(duel['id'], player_id, selected_card['id'], stat_number) + + return { + 'message': f'{author} You have selected {selected_card["name"]} with \ +{selected_card["stats"][stat_number]} in {["Power", "Charm", "Wit"][stat_number]} for the duel.', + 'attachment_urls': None] + } def delete_account(author: str) -> BotResponse: return { @@ -413,6 +458,11 @@ def generate_response(notification: ParsedNotification) -> BotResponse | None: author, notification['arguments'] ) + case 'select': + res = update_duel( + author, + notification['arguments'] + ) case _: pass -- 2.39.2