Implement continuous game operation with match recycling

- Modified _monitor_game_state to create new matches from old completed ones instead of stopping when fixture completes
- Enhanced _initialize_new_fixture to create fixtures from old matches when no new fixtures available
- Added helper methods for match selection and recycling:
  * _select_random_completed_matches(): Selects random completed matches
  * _create_matches_from_old_matches(): Creates new matches in existing fixture
  * _create_new_fixture_from_old_matches(): Creates new fixture from recycled matches
- Added comprehensive documentation explaining game flow and human intervention points
- Game now runs continuously until machine restart, recycling completed matches to maintain betting opportunities
parent 6b64015f
This diff is collapsed.
...@@ -375,22 +375,27 @@ class GamesThread(ThreadedComponent): ...@@ -375,22 +375,27 @@ class GamesThread(ThreadedComponent):
).count() ).count()
if active_count == 0: if active_count == 0:
logger.info(f"All matches completed for fixture {self.current_fixture_id}") logger.info(f"All matches completed for fixture {self.current_fixture_id} - creating new matches from old completed ones")
self.game_active = False
# Send game completed message # Instead of stopping the game, create 5 new matches from old completed matches
old_matches = self._select_random_completed_matches(5, session)
if old_matches:
self._create_matches_from_old_matches(self.current_fixture_id, old_matches, session)
logger.info(f"Created 5 new matches in fixture {self.current_fixture_id} from old completed matches")
else:
logger.warning("No old completed matches found - cannot create new matches")
# If no old matches available, stop the game
self.game_active = False
completed_message = Message( completed_message = Message(
type=MessageType.GAME_STATUS, type=MessageType.GAME_STATUS,
sender=self.name, sender=self.name,
data={ data={
"status": "completed", "status": "completed_no_old_matches",
"fixture_id": self.current_fixture_id, "fixture_id": self.current_fixture_id,
"timestamp": time.time() "timestamp": time.time()
} }
) )
self.message_bus.publish(completed_message) self.message_bus.publish(completed_message)
# Reset current fixture
self.current_fixture_id = None self.current_fixture_id = None
finally: finally:
...@@ -652,21 +657,19 @@ class GamesThread(ThreadedComponent): ...@@ -652,21 +657,19 @@ class GamesThread(ThreadedComponent):
return None return None
def _initialize_new_fixture(self) -> Optional[str]: def _initialize_new_fixture(self) -> Optional[str]:
"""Initialize a new fixture by finding the first one with no start_time set""" """Initialize a new fixture by finding the first one with no start_time set, or create one from old matches"""
try: try:
session = self.db_manager.get_session() session = self.db_manager.get_session()
try: try:
# Find the first fixture with no start_time set # First, try to find the first fixture with no start_time set
fixtures_no_start_time = session.query(MatchModel.fixture_id).filter( fixtures_no_start_time = session.query(MatchModel.fixture_id).filter(
MatchModel.start_time.is_(None), MatchModel.start_time.is_(None),
MatchModel.active_status == True MatchModel.active_status == True
).distinct().order_by(MatchModel.created_at.asc()).all() ).distinct().order_by(MatchModel.created_at.asc()).all()
if not fixtures_no_start_time: if fixtures_no_start_time:
return None
fixture_id = fixtures_no_start_time[0].fixture_id fixture_id = fixtures_no_start_time[0].fixture_id
logger.info(f"Initializing new fixture: {fixture_id}") logger.info(f"Initializing existing fixture with no start_time: {fixture_id}")
# Set start_time to now for all matches in this fixture # Set start_time to now for all matches in this fixture
now = datetime.utcnow() now = datetime.utcnow()
...@@ -683,6 +686,21 @@ class GamesThread(ThreadedComponent): ...@@ -683,6 +686,21 @@ class GamesThread(ThreadedComponent):
session.commit() session.commit()
return fixture_id return fixture_id
# No fixtures with no start_time found - create a new fixture from old completed matches
logger.info("No fixtures with no start_time found - creating new fixture from old completed matches")
old_matches = self._select_random_completed_matches(5, session)
if old_matches:
fixture_id = self._create_new_fixture_from_old_matches(old_matches, session)
if fixture_id:
logger.info(f"Created new fixture {fixture_id} from old completed matches")
return fixture_id
else:
logger.warning("Failed to create new fixture from old matches")
return None
else:
logger.warning("No old completed matches found - cannot create new fixture")
return None
finally: finally:
session.close() session.close()
...@@ -1700,6 +1718,129 @@ class GamesThread(ThreadedComponent): ...@@ -1700,6 +1718,129 @@ class GamesThread(ThreadedComponent):
except Exception as e: except Exception as e:
logger.error(f"Failed to send NEXT_MATCH: {e}") logger.error(f"Failed to send NEXT_MATCH: {e}")
def _select_random_completed_matches(self, count: int, session) -> List[MatchModel]:
"""Select random completed matches from the database"""
try:
# Get all completed matches (status = 'done')
completed_matches = session.query(MatchModel).filter(
MatchModel.status == 'done',
MatchModel.active_status == True
).all()
if len(completed_matches) < count:
logger.warning(f"Only {len(completed_matches)} completed matches found, requested {count}")
return completed_matches
# Select random matches
import random
selected_matches = random.sample(completed_matches, count)
logger.info(f"Selected {len(selected_matches)} random completed matches")
return selected_matches
except Exception as e:
logger.error(f"Failed to select random completed matches: {e}")
return []
def _create_matches_from_old_matches(self, fixture_id: str, old_matches: List[MatchModel], session):
"""Create new matches in the fixture by copying from old completed matches"""
try:
now = datetime.utcnow()
match_number = 1
for old_match in old_matches:
# Create a new match based on the old one
new_match = MatchModel(
match_number=match_number,
fighter1_township=old_match.fighter1_township,
fighter2_township=old_match.fighter2_township,
venue_kampala_township=old_match.venue_kampala_township,
start_time=now,
status='scheduled',
fixture_id=fixture_id,
filename=old_match.filename,
file_sha1sum=old_match.file_sha1sum,
active_status=True,
zip_filename=old_match.zip_filename,
zip_sha1sum=old_match.zip_sha1sum,
zip_upload_status='completed', # Assume ZIP is already available
fixture_active_time=int(now.timestamp())
)
session.add(new_match)
session.flush() # Get the ID
# Copy match outcomes
for outcome in old_match.outcomes:
new_outcome = MatchOutcomeModel(
match_id=new_match.id,
column_name=outcome.column_name,
float_value=outcome.float_value
)
session.add(new_outcome)
logger.debug(f"Created new match #{match_number} from old match #{old_match.match_number}")
match_number += 1
session.commit()
logger.info(f"Created {len(old_matches)} new matches in fixture {fixture_id}")
except Exception as e:
logger.error(f"Failed to create matches from old matches: {e}")
session.rollback()
raise
def _create_new_fixture_from_old_matches(self, old_matches: List[MatchModel], session) -> Optional[str]:
"""Create a new fixture with matches copied from old completed matches"""
try:
# Generate a unique fixture ID
import uuid
fixture_id = f"recycle_{uuid.uuid4().hex[:8]}"
now = datetime.utcnow()
match_number = 1
for old_match in old_matches:
# Create a new match based on the old one
new_match = MatchModel(
match_number=match_number,
fighter1_township=old_match.fighter1_township,
fighter2_township=old_match.fighter2_township,
venue_kampala_township=old_match.venue_kampala_township,
start_time=now,
status='scheduled',
fixture_id=fixture_id,
filename=old_match.filename,
file_sha1sum=old_match.file_sha1sum,
active_status=True,
zip_filename=old_match.zip_filename,
zip_sha1sum=old_match.zip_sha1sum,
zip_upload_status='completed', # Assume ZIP is already available
fixture_active_time=int(now.timestamp())
)
session.add(new_match)
session.flush() # Get the ID
# Copy match outcomes
for outcome in old_match.outcomes:
new_outcome = MatchOutcomeModel(
match_id=new_match.id,
column_name=outcome.column_name,
float_value=outcome.float_value
)
session.add(new_outcome)
logger.debug(f"Created match #{match_number} in new fixture {fixture_id} from old match #{old_match.match_number}")
match_number += 1
session.commit()
logger.info(f"Created new fixture {fixture_id} with {len(old_matches)} matches from old completed matches")
return fixture_id
except Exception as e:
logger.error(f"Failed to create new fixture from old matches: {e}")
session.rollback()
return None
def _cleanup(self): def _cleanup(self):
"""Perform cleanup operations""" """Perform cleanup operations"""
try: try:
......
# MbetterClient v1.2.11
Cross-platform multimedia client application
## Installation
1. Extract this package to your desired location
2. Run the executable file
3. The application will create necessary configuration files on first run
## System Requirements
- **Operating System**: Linux 6.16.3+deb14-amd64
- **Architecture**: x86_64
- **Memory**: 512 MB RAM minimum, 1 GB recommended
- **Disk Space**: 100 MB free space
## Configuration
The application stores its configuration and database in:
- **Windows**: `%APPDATA%\MbetterClient`
- **macOS**: `~/Library/Application Support/MbetterClient`
- **Linux**: `~/.config/MbetterClient`
## Web Interface
By default, the web interface is available at: http://localhost:5001
Default login credentials:
- Username: admin
- Password: admin
**Please change the default password after first login.**
## Support
For support and documentation, please visit: https://git.nexlab.net/mbetter/mbetterc
## Version Information
- Version: 1.2.11
- Build Date: zeiss
- Platform: Linux-6.16.3+deb14-amd64-x86_64-with-glibc2.41
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment