331 lines
11 KiB
C#
331 lines
11 KiB
C#
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",
|
||
"ОМЕГА", "АБСОЛЮТ", "ТЕРМИНАТОР", "БЕЗУМНЫЙ_М",
|
||
};
|
||
|
||
for (int i = 0; i < count; i++)
|
||
{
|
||
string name = highScoreNames[UnityEngine.Random.Range(0, highScoreNames.Count)];
|
||
highScoreNames.Remove(name);
|
||
|
||
int moves = UnityEngine.Random.Range(20, 80);
|
||
float time = UnityEngine.Random.Range(60f, 500f);
|
||
DateTime date = GetRandomDateTimeInRange();
|
||
|
||
// Все начальные записи - боты
|
||
PlayerStatsRecord record = new PlayerStatsRecord(name, time, moves, date, true);
|
||
AddRecord(record);
|
||
}
|
||
}
|
||
|
||
public void AddDailyRecord()
|
||
{
|
||
List<string> highScoreNames = new List<string>
|
||
{
|
||
"ПОБЕДИТЕЛЬ", "ЛЕГЕНДА", "ЧЕМПИОН", "МАСТЕР", "ГЕРОЙ",
|
||
"ЦАРЬ ГОРЫ", "УМНЯШКА300", "НЕЗНАКОМЕЦ", "ТЕНЬ", "НОМЕР_007",
|
||
"ОМЕГА", "АБСОЛЮТ", "ТЕРМИНАТОР", "БЕЗУМНЫЙ_М",
|
||
};
|
||
|
||
string name = highScoreNames[UnityEngine.Random.Range(0, highScoreNames.Count)];
|
||
|
||
int moves = UnityEngine.Random.Range(20, 80);
|
||
float time = UnityEngine.Random.Range(60f, 500f);
|
||
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);
|
||
}
|
||
} |