Skip to content

Instantly share code, notes, and snippets.

@Pro100AlexHell
Created September 3, 2019 01:37
Show Gist options
  • Save Pro100AlexHell/b97d706516fcc54badf08b3773ab309c to your computer and use it in GitHub Desktop.
Save Pro100AlexHell/b97d706516fcc54badf08b3773ab309c to your computer and use it in GitHub Desktop.
package com.AlexHell;
import java.util.ArrayList;
import java.util.Collections;
/**
* Ресолвер поиска Пути Коня с помощью BFS
* @implNote не потокобезопасный, с оптимизациям GC и CPU-cache
*/
public final class KnightPathResolver
{
/**
* Размеры поля
*/
private static final int MapWidth = 8;
private static final int MapHeight = 8;
/**
* Размер очереди (в реальности меньше чем размер поля, но сложно подобрать, гарантируем не-переполнение)
*/
private static final int PointQueueMaxCount = MapWidth * MapHeight;
/**
* Типы задаваемые в карте
* - отрицательные (-1 и -2) - можно посещать
* - положительные (или 0) - посещенные, с указанием Parent индекса, откуда пришли
*/
private static final int TypeNotFilled = -1;// Не посещено
private static final int TypeTarget = -2; // Целевая точка
private static final int TypeStart = Integer.MAX_VALUE; // Начальная точка (чтобы ее нельзя было посетить еще раз)
/**
* Карта типов сущностей в одномерном массиве
* (по индексации - см. реализацию)
* (по типам - см выше)
* (Y == 0 внизу, X == 0 слева)
*/
private static final int[] Entries = new int[MapWidth * MapHeight];
/**
* Очередь BFS: индексов точек
*/
private static final PointQueue PointQueue = new PointQueue(PointQueueMaxCount);
/**
* Поиск пути коня
* @param knightPathInputParams Входные данные (начало, конец)
* @return Путь (null - либо исходная равна конечной, либо пути не существует.. todo: могло бы быть если бы на карте были препятствия)
*/
public static KnightPathOutputParams Resolve(KnightPathInputParams knightPathInputParams)
{
if (knightPathInputParams.From.X == knightPathInputParams.To.X &&
knightPathInputParams.From.Y == knightPathInputParams.To.Y)
{
return null;
}
InitMap(knightPathInputParams);
PointQueue.Reset();
AddPointToQueue(knightPathInputParams.From.X, knightPathInputParams.From.Y);
boolean isResultFound = MainBfs();
if (isResultFound)
{
return FillResult(knightPathInputParams);
}
else
{
return null;
}
}
private static void InitMap(KnightPathInputParams knightPathInputParams)
{
for (int i = 0; i < MapWidth * MapHeight; i++)
{
Entries[i] = TypeNotFilled;
}
SetEntry(knightPathInputParams.From.X, knightPathInputParams.From.Y, TypeStart);
SetEntry(knightPathInputParams.To.X, knightPathInputParams.To.Y, TypeTarget);
}
private static void SetEntry(int x, int y, int value)
{
Entries[CombinePoint(x, y)] = value;
}
private static void AddPointToQueue(int x, int y)
{
PointQueue.AddLast(CombinePoint(x, y));
}
/**
* Основная работа BFS
* @return Признак: Найден результат, нужно построить путь
*/
private static boolean MainBfs()
{
boolean isResultFound = false;
while (PointQueue.HasItems())
{
int startPointIndex = PointQueue.PopFirst();
int startPointX = startPointIndex % MapWidth;
int startPointY = startPointIndex / MapWidth;
for (int dir = 0; dir <= KnightDirIndexMax; dir++)
{
int newPointIndex = GeneratePointShiftedByKnightDir(startPointX, startPointY, dir);
if (newPointIndex != -1)
{
int newEntry = Entries[newPointIndex];
if (newEntry == TypeTarget)
{
Entries[newPointIndex] = startPointIndex;
isResultFound = true;
break;
}
else if (newEntry == TypeNotFilled)
{
Entries[newPointIndex] = startPointIndex;
PointQueue.AddLast(newPointIndex);
}
// else - уже посещали
}
}
}
return isResultFound;
}
/**
* Всего 8 направлений хода коня (0-7)
*/
private static final int KnightDirIndexMax = 7;
/**
* Генерация новой точки с помощью хода коня
* @param startPointX Координаты начальной точки
* @param startPointY
* @param dir Индекс направления (0-7)
* @return Индекс новой точки (с проверкой выхода за границы карты, -1 если вышли за границы)
*/
private static int GeneratePointShiftedByKnightDir(int startPointX, int startPointY, int dir)
{
switch (dir)
{
/*
* +++++
* +++++
* ++K++
* ++++0
* +++++
*
* здесь и далее обозначено:
* K - исходная позиция
* 0 - целевая позиция
*/
case 0:
{
int dx = 2;
int dy = -1;
if (startPointX + dx >= MapWidth) return -1;
if (startPointY + dy < 0) return -1;
return CombinePointWithDxDy(startPointX, startPointY, dx, dy);
}
/*
* +++++
* ++++0
* ++K++
* +++++
* +++++
*/
case 1:
{
int dx = 2;
int dy = 1;
if (startPointX + dx >= MapWidth) return -1;
if (startPointY + dy >= MapHeight) return -1;
return CombinePointWithDxDy(startPointX, startPointY, dx, dy);
}
/*
* +++++
* 0++++
* ++K++
* +++++
* +++++
*/
case 2:
{
int dx = -2;
int dy = 1;
if (startPointX + dx < 0) return -1;
if (startPointY + dy >= MapHeight) return -1;
return CombinePointWithDxDy(startPointX, startPointY, dx, dy);
}
/*
* +++++
* +++++
* ++K++
* 0++++
* +++++
*/
case 3:
{
int dx = -2;
int dy = -1;
if (startPointX + dx < 0) return -1;
if (startPointY + dy < 0) return -1;
return CombinePointWithDxDy(startPointX, startPointY, dx, dy);
}
/*
* +++++
* +++++
* ++K++
* +++++
* +++0+
*/
case 4:
{
int dx = 1;
int dy = -2;
if (startPointX + dx >= MapWidth) return -1;
if (startPointY + dy < 0) return -1;
return CombinePointWithDxDy(startPointX, startPointY, dx, dy);
}
/*
* +++0+
* +++++
* ++K++
* +++++
* +++++
*/
case 5:
{
int dx = 1;
int dy = 2;
if (startPointX + dx >= MapWidth) return -1;
if (startPointY + dy >= MapHeight) return -1;
return CombinePointWithDxDy(startPointX, startPointY, dx, dy);
}
/*
* +0+++
* +++++
* ++K++
* +++++
* +++++
*/
case 6:
{
int dx = -1;
int dy = 2;
if (startPointX + dx < 0) return -1;
if (startPointY + dy >= MapHeight) return -1;
return CombinePointWithDxDy(startPointX, startPointY, dx, dy);
}
/*
* +++++
* +++++
* ++K++
* +++++
* +0+++
*/
default:
{
int dx = -1;
int dy = -2;
if (startPointX + dx < 0) return -1;
if (startPointY + dy < 0) return -1;
return CombinePointWithDxDy(startPointX, startPointY, dx, dy);
}
}
}
private static int CombinePointWithDxDy(int startPointX, int startPointY, int dx, int dy)
{
int newX = startPointX + dx;
int newY = startPointY + dy;
return CombinePoint(newX, newY);
}
private static int CombinePoint(int x, int y)
{
return y * MapWidth + x;
}
/**
* Заполнение результата
*/
private static KnightPathOutputParams FillResult(KnightPathInputParams knightPathInputParams)
{
KnightPathOutputParams knightPathOutputParams = new KnightPathOutputParams();
knightPathOutputParams.Points = new ArrayList<>(16); /** @todo пока без пула, но при желании можно */
knightPathOutputParams.Points.add(knightPathInputParams.To);
// т.к. уже найден каждый Parent - строим от конечной точки к начальной
int tempIndex = CombinePoint(knightPathInputParams.To.X, knightPathInputParams.To.Y);
while (true)
{
int parent = Entries[tempIndex];
if (parent == TypeStart)
{
break;
}
int parentX = parent % MapWidth;
int parentY = parent / MapWidth;
knightPathOutputParams.Points.add(new Point(parentX, parentY));
tempIndex = parent;
}
// инвертируем чтобы получилось от начальной точки к конечной
Collections.reverse(knightPathOutputParams.Points);
return knightPathOutputParams;
}
}
//#define DEBUG_TIMING // todo debug only
//#define DEBUG_LOADER_LOGS // todo debug only
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Assets.LoadableFiles.FileNameResolvers;
using Assets.LoadableFiles.SlicedArrayByte;
using Assets.LoadableFiles.WebOnly.WebLoaderToRamFromCache;
using Assets.SVG_Importer.Utils;
using UnityEngine;
namespace Assets.LoadableFiles
{
/// <summary>
/// Полноценный загрузчик файлов (с ресолвингом имен, со сложным кешем),
/// знающий о способах загрузки на разных платформах
/// - Абстрактная базовая форма
/// </summary>
/// <remarks>
/// Применим для загрузки большого кол-ва файлов с произвольным размером,
/// плюсы для WebGL:
/// * адекватное потребление памяти - переиспользуемые буферы в нашем пространстве
/// * (когда доступен IndexedDB) хорошая управляемость кешем
/// (например: без опроса изменений на сервере при наличии файла в IndexedDB, в отличие от UnityWebRequest,
/// хотя заголовки кеширования влияют на браузерный кеш - отдельная тема ревалидации)
/// </remarks>
/// <typeparam name="TInput">Подкласс входной информации о файле</typeparam>
/// <typeparam name="TResult">Подкласс результата загрузки контента файла</typeparam>
/// <typeparam name="TConversionResult">Подкласс результата конверсии контента файла</typeparam>
/// <typeparam name="TConversionParam">Тип параметра при конверсии</typeparam>
public abstract class PlatformAwareFileLoaderComplex<TInput, TResult, TConversionResult, TConversionParam>
where TInput : LoadableFileWrapped
where TResult : LoadedFile<TConversionResult, TConversionParam>
{
public long DebugTimeLoadBatch;
public long DebugTimeLoadSingle;
public long DebugTimeLoadSingleLoaderToRamFromCache;
/// <summary>
/// Сброс всех выданных SlicedArray
/// </summary>
public void ResetAllSlicedArrays()
{
PlatformAwareFileLoaderShared.BuilderOfSlicedArrayByte.Reset();
}
/// <summary>
/// Загрузка пакета файлов; для вызова в yield
/// </summary>
/// <param name="listLoadableFile">Список: информация о загружаемом файле</param>
/// <param name="listResult">Список для сохранения результатов (параллельно входному списку)</param>
public IEnumerator LoadBatch(List<TInput> listLoadableFile, List<TResult> listResult)
{
#if DEBUG_TIMING
long timeStart = DateTime.Now.Ticks;
#endif
if (listLoadableFile.Contains(null))
{
throw new Exception("PlatformAwareFileLoaderComplex::LoadBatch: listLoadableFile.Contains(null)");
}
// todo NOTE: это отладочное, в релизе из ресурсов не будет, но не удаляем
if (PlatformAwareFileLoaderShared.IsLoadFromResources)
{
yield return LoadBatchFromResources(listLoadableFile, listResult);
}
else
{
#if UNITY_WEBGL
yield return LoadBatchFromWebCache(listLoadableFile, listResult);
#elif UNITY_ANDROID || UNITY_IOS
yield return LoadBatchFromLocalMobilePack(listLoadableFile, listResult);
#else
yield return LoadBatchFromLocal(listLoadableFile, listResult);
#endif
}
#if DEBUG_TIMING
DebugTimeLoadBatch += (long)(new TimeSpan(DateTime.Now.Ticks - timeStart).TotalMilliseconds);
#endif
}
/// <summary>
/// Загрузка пакета файлов из Resources; для вызова в yield
/// </summary>
/// <remarks>Имя файла без задания расширения, но фактически в Resources находятся с расширением ".bytes"</remarks>
/// <param name="listLoadableFile">Список: информация о загружаемом файле</param>
/// <param name="listResult">Список для сохранения результатов (параллельно входному списку)</param>
private IEnumerator LoadBatchFromResources(List<TInput> listLoadableFile, List<TResult> listResult)
{
#if DEBUG_LOADER_LOGS
Debug.Log("LoadBatchFromResources");
#endif
for (int i = 0; i < listLoadableFile.Count; i++)
{
listResult.Add(LoadSingleFromResourcesAction(listLoadableFile[i]));
}
// (ждем конца фрейма, для пачки замедление не заметно, но без yield синтаксически не пройдет функция)
yield return null;
}
/// <summary>
/// Загрузка пакета файлов из кеша в WebGL; для вызова в yield
/// </summary>
/// <param name="listLoadableFile">Список: информация о загружаемом файле (размер не ограничен,
/// но должен быть адекватным из-за лимитов памяти на суммарные загруженные файлы и другие лимиты)</param>
/// <param name="listResult">Список для сохранения результатов (параллельно входному списку)</param>
private IEnumerator LoadBatchFromWebCache(List<TInput> listLoadableFile, List<TResult> listResult)
{
#if DEBUG_LOADER_LOGS
Debug.Log("LoadBatchFromWebCache");
#endif
List<LoaderToRamFromCacheBatch.LoadRequest> requestList = new List<LoaderToRamFromCacheBatch.LoadRequest>();
for (int i = 0; i < listLoadableFile.Count; i++)
{
LoadableFile loadableFile = listLoadableFile[i].LoadableFile;
string url = PlatformAwareFileLoaderShared.FileNameResolverForWeb.ResolveShortName(
loadableFile.ShortName, loadableFile.FileType);
// сразу создаем Handle массива байт, заполняем список результатов с использованием этого Handle,
// впоследствии будет заполнение реальных байт внутри массива
HandleOfSlicedArrayByte handleForResult =
PlatformAwareFileLoaderShared.BuilderOfSlicedArrayByte.BuildArray(
loadableFile.UncompressedSize);
//
listResult.Add(CreateResult(url, handleForResult));
requestList.Add(new LoaderToRamFromCacheBatch.LoadRequest(url, handleForResult));
}
yield return LoaderToRamFromCacheBatch.Instance.Load(requestList);
}
/// <summary>
/// Загрузка пакета файлов из пакета для мобильных; для вызова в yield
/// </summary>
/// <param name="listLoadableFile">Список: информация о загружаемом файле</param>
/// <param name="listResult">Список для сохранения результатов (параллельно входному списку)</param>
private IEnumerator LoadBatchFromLocalMobilePack(List<TInput> listLoadableFile, List<TResult> listResult)
{
#if DEBUG_LOADER_LOGS
Debug.Log("LoadBatchFromLocalMobilePack");
#endif
for (int i = 0; i < listLoadableFile.Count; i++)
{
listResult.Add(LoadSingleFromLocalMobilePackAction(listLoadableFile[i]));
}
// (ждем конца фрейма, для пачки замедление не заметно, но без yield синтаксически не пройдет функция)
yield return null;
}
/// <summary>
/// Загрузка пакета файлов из локального каталога; для вызова в yield
/// </summary>
/// <param name="listLoadableFile">Список: информация о загружаемом файле</param>
/// <param name="listResult">Список для сохранения результатов (параллельно входному списку)</param>
private IEnumerator LoadBatchFromLocal(List<TInput> listLoadableFile, List<TResult> listResult)
{
#if DEBUG_LOADER_LOGS
Debug.Log("LoadBatchFromLocal");
#endif
for (int i = 0; i < listLoadableFile.Count; i++)
{
listResult.Add(LoadSingleFromLocalAction(listLoadableFile[i]));
}
// (ждем конца фрейма, для пачки замедление не заметно, но без yield синтаксически не пройдет функция)
yield return null;
}
/// <summary>
/// Загрузка файла; для вызова в yield; строго для одиночной загрузки
/// </summary>
/// <param name="loadableFile">Информация о загружаемом файле</param>
/// <param name="result">Для сохранения результата</param>
public IEnumerator LoadSingle(TInput loadableFile, CoroutineResult<TResult> result)
{
#if DEBUG_TIMING
long timeStart2 = DateTime.Now.Ticks;
#endif
if (loadableFile == null)
{
throw new Exception("PlatformAwareFileLoaderComplex::LoadSingle: loadableFile == null");
}
// todo NOTE: это отладочное, в релизе из ресурсов не будет, но не удаляем
if (PlatformAwareFileLoaderShared.IsLoadFromResources)
{
yield return LoadSingleFromResources(loadableFile, result);
}
else
{
#if UNITY_WEBGL
yield return LoadSingleFromWebCache(loadableFile, result);
#elif UNITY_ANDROID || UNITY_IOS
yield return LoadSingleFromLocalMobilePack(loadableFile, result);
#else
yield return LoadSingleFromLocal(loadableFile, result);
#endif
}
#if DEBUG_TIMING
DebugTimeLoadSingle += (long)(new TimeSpan(DateTime.Now.Ticks - timeStart2).TotalMilliseconds);
#endif
}
/// <summary>
/// Загрузка файла из Resources; для вызова в yield; строго для одиночной загрузки
/// </summary>
/// <remarks>Имя файла без задания расширения, но фактически в Resources находятся с расширением ".bytes"</remarks>
/// <param name="loadableFile">Информация о загружаемом файле</param>
/// <param name="result">Для сохранения результата</param>
private IEnumerator LoadSingleFromResources(TInput loadableFile, CoroutineResult<TResult> result)
{
#if DEBUG_LOADER_LOGS
Debug.Log("LoadSingleFromResources");
#endif
result.Value = LoadSingleFromResourcesAction(loadableFile);
// (ждем конца фрейма, получая замедление, но без yield синтаксически не пройдет функция)
yield return null;
}
/// <summary>
/// Реальная загрузка файла из Resources; строго для одиночной загрузки
/// </summary>
/// <remarks>Имя файла без задания расширения, но фактически в Resources находятся с расширением ".bytes"</remarks>
/// <param name="loadableFile">Информация о загружаемом файле</param>
/// <returns>Результат</returns>
private TResult LoadSingleFromResourcesAction(TInput loadableFile)
{
string fileName = loadableFile.LoadableFile.ShortName; // (без ресолвинга имени т.к. все в плоском каталоге Resources)
// (хоть тут и выделяется byte[] лишний - по-другому не сделать, а интерфейс далее общий под Handle)
byte[] bytes;
try
{
TextAsset binAsset = Resources.Load(fileName) as TextAsset;
bytes = binAsset.bytes;
}
catch (Exception)
{
throw new Exception("PlatformAwareFileLoaderComplex::LoadSingleFromResourcesAction: Not found FileName = '" + fileName + "'");
}
// (UncompressedSize не задан в этом случае, поэтому под размер считанного массива)
HandleOfSlicedArrayByte handleForResult =
PlatformAwareFileLoaderShared.BuilderOfSlicedArrayByte.BuildArray(bytes.Length);
Array.Copy(bytes, 0,
handleForResult.TotalBlock, handleForResult.StartIndex, bytes.Length
);
return CreateResult(fileName, handleForResult);
}
/// <summary>
/// Загрузка файла из кеша в WebGL; для вызова в yield; строго для одиночной загрузки
/// </summary>
/// <param name="loadableFile">Информация о загружаемом файле</param>
/// <param name="result">Для сохранения результата</param>
private IEnumerator LoadSingleFromWebCache(TInput loadableFile, CoroutineResult<TResult> result)
{
#if DEBUG_LOADER_LOGS
Debug.Log("LoadSingleFromWebCache");
#endif
#if DEBUG_TIMING
long timeStart1 = DateTime.Now.Ticks;
#endif
string url = PlatformAwareFileLoaderShared.FileNameResolverForWeb.ResolveShortName(
loadableFile.LoadableFile.ShortName, loadableFile.LoadableFile.FileType);
HandleOfSlicedArrayByte handleForResult =
PlatformAwareFileLoaderShared.BuilderOfSlicedArrayByte.BuildArray(
loadableFile.LoadableFile.UncompressedSize);
LoaderToRamFromCache loader = PlatformAwareFileLoaderShared.SingleLoaderToRamFromCache;
loader.StartLoadFile(url, handleForResult);
bool isComplete;
while (true)
{
LoaderToRamFromCacheUpdateResult updateResult = loader.Update();
if (updateResult == LoaderToRamFromCacheUpdateResult.KeepWaiting)
{
yield return null;
}
else
{
isComplete = (updateResult == LoaderToRamFromCacheUpdateResult.Complete);
break;
}
}
if (isComplete)
{
// debug only
//Debug.Log("Load to RAM complete, length = " + handleForResult.Length + ", url = " + url);
}
else
{
throw new Exception("PlatformAwareFileLoaderComplex::LoadSingleFromWebCache: Load to RAM ERROR, url = '" + url + "'");
}
result.Value = CreateResult(url, handleForResult);
#if DEBUG_TIMING
DebugTimeLoadSingleLoaderToRamFromCache += (long)(new TimeSpan(DateTime.Now.Ticks - timeStart1).TotalMilliseconds);
#endif
}
/// <summary>
/// Загрузка файла из локального каталога; для вызова в yield; строго для одиночной загрузки
/// </summary>
/// <param name="loadableFile">Информация о загружаемом файле</param>
/// <param name="result">Для сохранения результата</param>
private IEnumerator LoadSingleFromLocal(TInput loadableFile, CoroutineResult<TResult> result)
{
#if DEBUG_LOADER_LOGS
Debug.Log("LoadSingleFromLocal");
#endif
result.Value = LoadSingleFromLocalAction(loadableFile);
// (ждем конца фрейма, получая замедление, но без yield синтаксически не пройдет функция)
yield return null;
}
/// <summary>
/// Реальная загрузка файла из локального каталога; строго для одиночной загрузки
/// </summary>
/// <param name="loadableFile">Информация о загружаемом файле</param>
/// <returns>Результат</returns>
private TResult LoadSingleFromLocalAction(TInput loadableFile)
{
ILoadableFileNameResolver fileNameResolver = PlatformAwareFileLoaderShared.FileNameResolverForWindows;
string fileName = fileNameResolver.ResolveShortName(loadableFile.LoadableFile.ShortName, loadableFile.LoadableFile.FileType);
HandleOfSlicedArrayByte handleForResult =
PlatformAwareFileLoaderShared.BuilderOfSlicedArrayByte.BuildArray(
loadableFile.LoadableFile.UncompressedSize);
try
{
using (FileStream file = File.OpenRead(fileName))
{
// (должны быть равны т.к. буфер выделяется строго под этот размер, и зачем выделять больший
// если убирается в меньший; ошибка консистентности размеров)
if (file.Length != loadableFile.LoadableFile.UncompressedSize)
{
throw new Exception("PlatformAwareFileLoaderComplex::LoadSingleFromLocalAction: Actual length (" + file.Length + ") is not equals expected (" + loadableFile.LoadableFile.UncompressedSize + "), FileName = '" + fileName + "'");
}
file.Read(handleForResult.TotalBlock, handleForResult.StartIndex, handleForResult.Length);
}
}
catch (FileNotFoundException)
{
throw new Exception("PlatformAwareFileLoaderComplex::LoadSingleFromLocalAction: Not found FileName = '" + fileName + "'");
}
return CreateResult(fileName, handleForResult);
}
/// <summary>
/// Загрузка файла из пакета для мобильных; для вызова в yield; строго для одиночной загрузки
/// </summary>
/// <param name="loadableFile">Информация о загружаемом файле</param>
/// <param name="result">Для сохранения результата</param>
private IEnumerator LoadSingleFromLocalMobilePack(TInput loadableFile, CoroutineResult<TResult> result)
{
#if DEBUG_LOADER_LOGS
Debug.Log("LoadSingleFromLocalMobilePack");
#endif
result.Value = LoadSingleFromLocalMobilePackAction(loadableFile);
// (ждем конца фрейма, получая замедление, но без yield синтаксически не пройдет функция)
yield return null;
}
/// <summary>
/// Реальная загрузка файла из пакета для мобильных; строго для одиночной загрузки
/// </summary>
/// <param name="loadableFile">Информация о загружаемом файле</param>
/// <returns>Результат</returns>
private TResult LoadSingleFromLocalMobilePackAction(TInput loadableFile)
{
HandleOfSlicedArrayByte handleForResult =
PlatformAwareFileLoaderShared.MobileResourcePackLoader.ReadLoadableFile(loadableFile.LoadableFile);
return CreateResult(loadableFile.LoadableFile.ShortName, handleForResult);
}
/// <summary>
/// Создание результата загрузки одного файла
/// </summary>
/// <param name="fileName">Имя файла (возможно URL) для отладки</param>
/// <param name="handleForResult">Handle массива байт</param>
protected abstract TResult CreateResult(string fileName, HandleOfSlicedArrayByte handleForResult);
}
}
package pandemicaserver.Inventory;
import Assets.Network.Proto.ConsumableTypeOuterClass.ConsumableType;
import Assets.Network.Proto.UserInventoryOuterClass.AmmoEntry;
import Assets.Network.Proto.UserInventoryOuterClass.ConsumableEntry;
import Assets.Network.Proto.UserInventoryOuterClass.WeaponEntry;
import Assets.Network.Proto.WeaponTypeAndAbilityType.WeaponType;
import pandemicaserver.Utils.FuncUtils;
import java.util.*;
/**
* Инвентарь пользователя
*/
public class UserInventory implements IUserInventoryModifiable, IUserInventoryReadOnly
{
/**
* Карта расходных предметов: Тип -> Кол-во зарядов
*/
private final Map<ConsumableType, Integer> Consumables = new HashMap<>();
/**
* Карта оружий: Тип -> Кол-во патронов (-1 = бесконечное оружие)
*/
private final Map<WeaponType, Integer> Weapons = new HashMap<>();
/**
* Карта незаряженных патронов: Тип -> Кол-во патронов
* (когда нет подходящего оружия)
* @todo использовать позже
*/
private final Map<WeaponType, Integer> Ammos = new HashMap<>();
public UserInventory()
{
}
/**
* Добавление (или уменьшение) расходных предметов
* @param type Тип
* @param add Кол-во добавленных зарядов (возможно отрицательное)
* @return Признак: Успешно (иначе - не достаточно зарядов)
*/
@Override
public boolean AddConsumables(ConsumableType type, int add)
{
return AddInMap(Consumables, type, add);
}
/**
* Добавление (или уменьшение) патронов к оружию
* @param type Тип
* @param add Кол-во патронов (возможно отрицательное)
* @return Признак: Успешно (иначе - не достаточно патронов)
*/
@Override
public boolean AddWeaponAndAmmo(WeaponType type, int add)
{
return AddInMap(Weapons, type, add);
}
/**
* Добавление (или уменьшение) элементов в карте
* @param <T> Класс элемента ключа (типа)
* @param map Карта для изменения
* @param key Ключ - Тип
* @param add Кол-во добавленных зарядов (возможно отрицательное)
* @return Признак: Успешно (иначе - не достаточно зарядов и не отняли нисколько)
*/
private <T> boolean AddInMap(Map<T, Integer> map, T key, int add)
{
int newCount = map.getOrDefault(key, 0) + add;
if (newCount < 0) return false;
if (newCount == 0)
{
map.remove(key);
}
else
{
map.put(key, newCount);
}
return true;
}
/**
* Добавление оружия с бесконечным числом патронов
* @param type Тип
*/
@Override
public void AddWeaponUnlimited(WeaponType type)
{
Weapons.put(type, -1);
}
/**
* Получение кол-ва расходных предметов
* @param type Тип
*/
@Override
public int GetConsumablesCount(ConsumableType type)
{
return Consumables.getOrDefault(type, 0);
}
/**
* Получение кол-ва патронов в оружии (в том числе отдельных без оружия)
* @param type Тип
*/
@Override
public int GetWeaponAmmoCount(WeaponType type)
{
return Weapons.getOrDefault(type, 0) + Ammos.getOrDefault(type, 0);
}
/**
* Перевод коллекции расходных предметов в список элементов protobuf
* @return
*/
public List<ConsumableEntry> ConsumablesToProtoList()
{
return FuncUtils.ConvertMapToList(Consumables,
(key, value) -> ConsumableEntry.newBuilder()
.setType(key)
.setCount(value)
.build()
);
}
/**
* Перевод коллекции оружий в список элементов protobuf
* @return
*/
public List<WeaponEntry> WeaponsToProtoList()
{
return FuncUtils.ConvertMapToList(Weapons,
(key, value) -> WeaponEntry.newBuilder()
.setType(key)
.setCount(value)
.build()
);
}
/**
* Перевод коллекции незаряженных патронов в список элементов protobuf
* @return
*/
public List<AmmoEntry> AmmosToProtoList()
{
return FuncUtils.ConvertMapToList(Ammos,
(key, value) -> AmmoEntry.newBuilder()
.setType(key)
.setCount(value)
.build()
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment