chip-gameV3/Assets/Scripts/Common/Statistics/PlayerStatsService.cs
sega 8a6d23b77d Реорг Scripts
Разделено на 4 модуля
- Menu
- Gameplay
- Settings
- Common
2026-07-15 10:49:55 +03:00

416 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using UnityEngine;
using SqliteConnection = Microsoft.Data.Sqlite.SqliteConnection;
public class PlayerStatsService : IDisposable
{
private string _connectionString;
private IDbConnection _dbConnection;
private string dbPath;
public PlayerStatsService()
{
dbPath = Path.Combine(Application.persistentDataPath, "player_stats.db");
_connectionString = $"Data Source={dbPath};Cache=Shared;Mode=ReadWriteCreate";
}
// Инициализация базы данных и создание таблицы
public void Initialize()
{
try
{
bool isNewDatabase = !DatabaseExists();
SQLitePCL.Batteries_V2.Init();
_dbConnection = new SqliteConnection(_connectionString);
_dbConnection.Open();
CreateTableIfNotExists();
if (isNewDatabase)
{
InitialRecords(8);
}
}
catch (Exception ex)
{
Debug.LogError($"Failed to initialize PlayerStatsService: {ex.Message}");
throw;
}
}
private bool DatabaseExists()
{
return File.Exists(dbPath);
}
private void CreateTableIfNotExists()
{
string createTableQuery = @"
CREATE TABLE IF NOT EXISTS player_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_name TEXT NOT NULL,
completion_time_sec REAL NOT NULL,
moves_count INTEGER NOT NULL,
completed_at DATETIME NOT NULL,
is_synced BOOLEAN NOT NULL DEFAULT 0,
is_bot BOOLEAN NOT NULL DEFAULT 0 -- Новое поле
)";
using (IDbCommand dbCommand = _dbConnection.CreateCommand())
{
dbCommand.CommandText = createTableQuery;
dbCommand.ExecuteNonQuery();
}
CreateIndexes();
}
private void CreateIndexes()
{
string[] indexQueries = {
"CREATE INDEX IF NOT EXISTS idx_time_moves ON player_stats (completion_time_sec, moves_count)",
"CREATE INDEX IF NOT EXISTS idx_completed_at ON player_stats (completed_at)"
};
foreach (string query in indexQueries)
{
using (IDbCommand dbCommand = _dbConnection.CreateCommand())
{
dbCommand.CommandText = query;
dbCommand.ExecuteNonQuery();
}
}
}
// Добавление новой записи
public void AddRecord(PlayerStatsRecord record)
{
if (record == null)
throw new ArgumentNullException(nameof(record));
string insertQuery = @"
INSERT INTO player_stats (player_name, completion_time_sec, moves_count, completed_at, is_synced, is_bot)
VALUES (@playerName, @completionTime, @movesCount, @completedAt, @isSynced, @isBot)";
using (IDbCommand dbCommand = _dbConnection.CreateCommand())
{
dbCommand.CommandText = insertQuery;
AddParameter(dbCommand, "@playerName", record.PlayerName);
AddParameter(dbCommand, "@completionTime", record.CompletionTimeSec);
AddParameter(dbCommand, "@movesCount", record.MovesCount);
AddParameter(dbCommand, "@completedAt", record.CompletedAt);
AddParameter(dbCommand, "@isSynced", record.IsSynced);
AddParameter(dbCommand, "@isBot", record.IsBot); // Новый параметр
dbCommand.ExecuteNonQuery();
}
}
// Удобный метод для добавления записи без создания объекта вручную
public void AddRecord(string playerName, float completionTimeSec, int movesCount, bool isBot = false)
{
var record = new PlayerStatsRecord(playerName, completionTimeSec, movesCount, isBot);
AddRecord(record);
}
// Получение N лучших игроков
public List<PlayerStatsRecord> GetTopPlayers(int count = 3, bool includeBots = true)
{
var records = new List<PlayerStatsRecord>();
string whereClause = includeBots ? "" : " WHERE is_bot = 0";
string selectQuery = $@"
SELECT id, player_name, completion_time_sec, moves_count, completed_at, is_synced, is_bot
FROM player_stats
{whereClause}
ORDER BY completion_time_sec ASC, moves_count ASC
LIMIT @limit";
using (IDbCommand dbCommand = _dbConnection.CreateCommand())
{
dbCommand.CommandText = selectQuery;
AddParameter(dbCommand, "@limit", count);
using (IDataReader reader = dbCommand.ExecuteReader())
{
while (reader.Read())
{
records.Add(CreateRecordFromReader(reader));
}
}
}
return records;
}
// Получение места записи в рейтинге
public int GetRecordRank(PlayerStatsRecord record)
{
if (record == null)
throw new ArgumentNullException(nameof(record));
string rankQuery = @"
SELECT COUNT(*) + 1 as rank
FROM player_stats
WHERE completion_time_sec < @completionTime
OR (completion_time_sec = @completionTime AND moves_count < @movesCount)";
using (IDbCommand dbCommand = _dbConnection.CreateCommand())
{
dbCommand.CommandText = rankQuery;
AddParameter(dbCommand, "@completionTime", record.CompletionTimeSec);
AddParameter(dbCommand, "@movesCount", record.MovesCount);
var result = dbCommand.ExecuteScalar();
return Convert.ToInt32(result);
}
}
// Получение общего количества записей
public int GetTotalRecordsCount()
{
string countQuery = "SELECT COUNT(*) FROM player_stats";
using (IDbCommand dbCommand = _dbConnection.CreateCommand())
{
dbCommand.CommandText = countQuery;
var result = dbCommand.ExecuteScalar();
return Convert.ToInt32(result);
}
}
public PlayerStatsRecord GetLatestRecord()
{
string selectQuery = @"
SELECT id, player_name, completion_time_sec, moves_count, completed_at, is_synced, is_bot
FROM player_stats
ORDER BY completed_at DESC
LIMIT 1";
using (IDbCommand dbCommand = _dbConnection.CreateCommand())
{
dbCommand.CommandText = selectQuery;
using (IDataReader reader = dbCommand.ExecuteReader())
{
if (reader.Read())
{
return CreateRecordFromReader(reader);
}
}
}
return null; // Если записей нет
}
// Вспомогательный метод для создания записи из DataReader
private PlayerStatsRecord CreateRecordFromReader(IDataReader reader)
{
return new PlayerStatsRecord
{
Id = reader.GetInt32(0),
PlayerName = reader.GetString(1),
CompletionTimeSec = reader.GetFloat(2), // Изменено с GetInt32 на GetFloat
MovesCount = reader.GetInt32(3),
CompletedAt = reader.GetDateTime(4),
IsSynced = reader.GetBoolean(5),
IsBot = reader.GetBoolean(6) // Новое поле
};
}
// Вспомогательный метод для добавления параметров
private void AddParameter(IDbCommand command, string parameterName, object value)
{
var p = command.CreateParameter();
p.ParameterName = parameterName;
if (value is DateTime dt)
{
p.DbType = DbType.DateTime; // хранится как TEXT (ISO8601) у Microsoft.Data.Sqlite — читаться будет через GetDateTime
p.Value = dt;
}
else if (value is bool b)
{
p.DbType = DbType.Boolean; // в SQLite это 0/1
p.Value = b;
}
else
{
p.Value = value ?? DBNull.Value;
}
command.Parameters.Add(p);
}
// Очистка всех записей (для тестирования)
public void ClearAllRecords()
{
string deleteQuery = "DELETE FROM player_stats";
using (IDbCommand dbCommand = _dbConnection.CreateCommand())
{
dbCommand.CommandText = deleteQuery;
dbCommand.ExecuteNonQuery();
}
}
// Закрытие соединения
public void Dispose()
{
_dbConnection?.Close();
_dbConnection?.Dispose();
}
private void InitialRecords(int count)
{
List<string> highScoreNames = new List<string>
{
"ПОБЕДИТЕЛЬ", "ЛЕГЕНДА", "ЧЕМПИОН", "МАСТЕР", "ГЕРОЙ",
"ЦАРЬ ГОРЫ", "УМНЯШКА300", "НЕЗНАКОМЕЦ", "ТЕНЬ", "НОМЕР_007",
"ОМЕГА", "АБСОЛЮТ", "ТЕРМИНАТОР", "БЕЗУМНЫЙ_М",
};
if (count > highScoreNames.Count)
{
count = highScoreNames.Count;
Debug.Log("Запрогено больше имен, чем есть в списке. Взято количество доступных имен.");
}
for (int i = 0; i < count; i++)
{
string name = highScoreNames[UnityEngine.Random.Range(0, highScoreNames.Count)];
highScoreNames.Remove(name);
int moves = UnityEngine.Random.Range(60, 200);
float time = UnityEngine.Random.Range(120f, 800f);
DateTime date = GetRandomDateTimeInRange();
// Все начальные записи - боты
PlayerStatsRecord record = new PlayerStatsRecord(name, time, moves, date, true);
AddRecord(record);
}
}
public void AddDailyRecord()
{
List<string> highScoreNames = new List<string>
{
"АНЯ",
"ВАСЯ",
"КОЛЯ",
"МИША",
"ТАНЯ",
"СВЕТА",
"ИГОРЬ",
"ОЛЕГ",
"МАКСИМ",
"ДИМА",
"ЛЕХА ТОП",
"ДАНЯ ПРО",
"ААА",
"ГРОЗА",
"ЧАЙ",
"Я",
"РОБЛОКС",
"НАГИБ",
"БОБО",
"",
};
string name = highScoreNames[UnityEngine.Random.Range(0, highScoreNames.Count)];
int moves = UnityEngine.Random.Range(60, 200);
float time = UnityEngine.Random.Range(120f, 800f);
DateTime date = DateTime.Now;
PlayerStatsRecord record = new PlayerStatsRecord(name, time, moves, date, true);
AddRecord(record);
}
/* public static DateTime GetRandomDateTimeInRange()
{
System.Random random = new System.Random();
// Диапазон дней: от сегодня-5 до сегодня
DateTime today = DateTime.Today;
DateTime minDate = today.AddDays(-5);
int daysRange = (today - minDate).Days;
DateTime randomDay = minDate.AddDays(random.Next(daysRange + 1)); // +1 чтобы включить сегодня
// Диапазон часов: от 11 до 17
int randomHour = random.Next(11, 18); // 18 не включается, так что получим 11-17
int randomMinute = random.Next(0, 60);
int randomSecond = random.Next(0, 60);
return new DateTime(randomDay.Year, randomDay.Month, randomDay.Day,
randomHour, randomMinute, randomSecond);
}*/
public static DateTime GetRandomDateTimeInRange()
{
System.Random random = new System.Random();
DateTime now = DateTime.Now;
// Максимальная дата/время: {сейчас} - 5 часов
DateTime maxDateTime = now.AddHours(-5);
// Если время maxDateTime выходит за рамки 11-17 часов, корректируем
if (maxDateTime.Hour < 11)
{
// Если меньше 11 утра, смещаем на предыдущий день 17:00
maxDateTime = maxDateTime.Date.AddDays(-1).AddHours(17);
}
else if (maxDateTime.Hour > 17)
{
// Если больше 17:00, устанавливаем на сегодня 17:00
maxDateTime = maxDateTime.Date.AddHours(17);
}
// Минимальная дата: 5 дней назад от maxDateTime
DateTime minDateTime = maxDateTime.AddDays(-5);
// Корректируем minDateTime, чтобы он тоже был в диапазоне 11-17 часов
if (minDateTime.Hour < 11)
{
minDateTime = minDateTime.Date.AddHours(11);
}
else if (minDateTime.Hour > 17)
{
minDateTime = minDateTime.Date.AddHours(17);
}
// Генерируем случайные даты в диапазоне с учетом часов 11-17
List<DateTime> validDates = new List<DateTime>();
// Перебираем все дни в диапазоне
for (DateTime date = minDateTime.Date; date <= maxDateTime.Date; date = date.AddDays(1))
{
// Для каждого дня добавляем только часы с 11 до 17
for (int hour = 11; hour <= 17; hour++)
{
DateTime candidate = date.AddHours(hour).AddMinutes(random.Next(0, 60)).AddSeconds(random.Next(0, 60));
// Проверяем, что время находится в нашем диапазоне
if (candidate >= minDateTime && candidate <= maxDateTime)
{
validDates.Add(candidate);
}
}
}
// Если есть подходящие даты, возвращаем случайную
if (validDates.Count > 0)
{
return validDates[random.Next(validDates.Count)];
}
// Если нет подходящих дат (очень узкий диапазон), возвращаем minDateTime
return minDateTime;
}
}