From db2d3e980b210ccb9551bc797b6e0ba700fb336f Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Fri, 10 Sep 2021 22:57:29 +0400 Subject: [PATCH 01/12] added logging for product-checkout analyzing --- pom.xml | 6 +- .../psplayer/PerfectStorePlayer.java | 175 +++++++++++------- 2 files changed, 114 insertions(+), 67 deletions(-) diff --git a/pom.xml b/pom.xml index f13558a..ab54160 100644 --- a/pom.xml +++ b/pom.xml @@ -88,9 +88,9 @@ org.apache.maven.plugins maven-compiler-plugin - 8 - 8 - + 10 + 10 + diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index c50db83..dafca24 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -10,8 +10,7 @@ import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.api.PerfectStoreEndpointApi; import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; @Component @@ -20,6 +19,9 @@ public class PerfectStorePlayer implements ApplicationListener customersOnCheckline = new HashSet<>(); + private Map soldProducts = new HashMap<>(); // productId->product. product.inStock contains quantity + public PerfectStorePlayer(@Value("${rs.endpoint:http://localhost:9080}") String serverUrl) { this.serverUrl = serverUrl; } @@ -42,82 +44,38 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { cnt += 1; if (cnt % 120 == 0) { log.info("Пройден " + cnt + " тик"); +// log.info("Ответ с предыдущего шага="+currentWorldResponse); } if (currentWorldResponse == null) { currentWorldResponse = psApiClient.loadWorld(); } - CurrentTickRequest request = new CurrentTickRequest(); - - List hireEmployeeCommands = new ArrayList<>(); - request.setHireEmployeeCommands(hireEmployeeCommands); - // Смотрим на каких кассах нет кассира (либо не был назначен, либо ушел с кассы отдыхать), нанимаем новых кассиров и ставим на эти кассы. - // Нанимаем самых опытных! - // TODO hire only if here are too many customers - currentWorldResponse.getCheckoutLines().stream().filter(line -> line.getEmployeeId() == null).forEach(line -> { - HireEmployeeCommand hireEmployeeCommand = new HireEmployeeCommand(); - hireEmployeeCommand.setCheckoutLineId(line.getId()); - hireEmployeeCommand.setExperience(HireEmployeeCommand.ExperienceEnum.SENIOR); - hireEmployeeCommands.add(hireEmployeeCommand); - }); - request.setHireEmployeeCommands(hireEmployeeCommands); - - // готовимся закупать товар на склад и выставлять его на полки - ArrayList buyStockCommands = new ArrayList<>(); - request.setBuyStockCommands(buyStockCommands); - - ArrayList putOnRackCellCommands = new ArrayList<>(); - request.setPutOnRackCellCommands(putOnRackCellCommands); - - List stock = currentWorldResponse.getStock(); - List rackCells = currentWorldResponse.getRackCells(); - - // Обходим торговый зал и смотрим какие полки пустые. Выставляем на них товар. - currentWorldResponse.getRackCells().stream().filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)).forEach(rack -> { - Product producttoPutOnRack = null; - if (rack.getProductId() == null) { - List productsOnRack = rackCells.stream().filter(r -> r.getProductId() != null).map(RackCell::getProductId).collect(Collectors.toList()); - productsOnRack.addAll(putOnRackCellCommands.stream().map(c -> c.getProductId()).collect(Collectors.toList())); - producttoPutOnRack = stock.stream().filter(product -> !productsOnRack.contains(product.getId())).findFirst().orElse(null); - } - else { - producttoPutOnRack = stock.stream().filter(product -> product.getId().equals(rack.getProductId())).findFirst().orElse(null); - } - - Integer productQuantity = rack.getProductQuantity(); - if (productQuantity == null) { - productQuantity = 0; - } - - // Вначале закупим товар на склад. Каждый ход закупать товар накладно, но ведь это тестовый игрок. - Integer orderQuantity = rack.getCapacity() - productQuantity; - if (producttoPutOnRack.getInStock() < orderQuantity) { - BuyStockCommand command = new BuyStockCommand(); - command.setProductId(producttoPutOnRack.getId()); - command.setQuantity(100); - buyStockCommands.add(command); - } - - // Далее разложим на полки. И сформируем цену. Накинем 10 рублей к оптовой цене - PutOnRackCellCommand command = new PutOnRackCellCommand(); - command.setProductId(producttoPutOnRack.getId()); - command.setRackCellId(rack.getId()); - command.setProductQuantity(orderQuantity); - if (producttoPutOnRack.getSellPrice() == null) { - command.setSellPrice(producttoPutOnRack.getStockPrice() + 10); - } - putOnRackCellCommands.add(command); - - }); + CurrentTickRequest request = createNextMove(currentWorldResponse); currentWorldResponse = psApiClient.tick(request); + collectDataFromAnswer(currentWorldResponse); } while (!currentWorldResponse.isGameOver()); // Если пришел Game Over, значит все время игры закончилось. Пора считать прибыль log.info("Я заработал " + (currentWorldResponse.getIncome() - currentWorldResponse.getSalaryCosts() - currentWorldResponse.getStockCosts()) + "руб."); + log.info("Total ticks:" + cnt); + log.info("Sold products(total" + soldProducts.size() + "):" + soldProducts); + var awaitingCheckoutCustomersCount = currentWorldResponse.getCustomers().stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.WAIT_CHECKOUT)).count(); + var atCheckoutCustomersCount = currentWorldResponse.getCustomers().stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.AT_CHECKOUT)).count(); + var inHallCustomersCount = currentWorldResponse.getCustomers().stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.IN_HALL)).count(); + log.info(String.format("Customers entered to checkline=%s, at checkout=%s, awaiting=%s, in hall=%s, total=%s", + customersOnCheckline.size(), + awaitingCheckoutCustomersCount, + atCheckoutCustomersCount, + inHallCustomersCount, + currentWorldResponse.getCustomers().size() + )); } catch (ApiException e) { log.error(e.getMessage(), e); @@ -125,6 +83,95 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { } + private void collectDataFromAnswer(CurrentWorldResponse worldResponse) { + var customers = worldResponse.getCustomers(); + var awaitingCheckoutCustomers = customers.stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.WAIT_CHECKOUT)). + collect(Collectors.toList()); + var atCheckoutCustomers = customers.stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.AT_CHECKOUT)). + collect(Collectors.toList()); + var awaitingCheckoutProducts = awaitingCheckoutCustomers.stream(). + map(it -> it.getBasket()). + collect(ArrayList::new, List::addAll, List::addAll); + for (ProductInBasket product : awaitingCheckoutProducts) { + if (soldProducts.containsKey(product.getId())) { + var countedProduct = soldProducts.get(product.getId()); + countedProduct.setProductCount(countedProduct.getProductCount() + product.getProductCount()); + } else { + soldProducts.put(product.getId(), product); + } + } + customersOnCheckline.addAll(atCheckoutCustomers); + + } + + private CurrentTickRequest createNextMove(CurrentWorldResponse currentWorldResponse) { + CurrentTickRequest request = new CurrentTickRequest(); + + // TODO hire only if here are too many customers +// List hireEmployeeCommands = new ArrayList<>(); +// request.setHireEmployeeCommands(hireEmployeeCommands); + // Смотрим на каких кассах нет кассира (либо не был назначен, либо ушел с кассы отдыхать), нанимаем новых кассиров и ставим на эти кассы. + // Нанимаем самых опытных! +// currentWorldResponse.getCheckoutLines().stream().filter(line -> line.getEmployeeId() == null).forEach(line -> { +// HireEmployeeCommand hireEmployeeCommand = new HireEmployeeCommand(); +// hireEmployeeCommand.setCheckoutLineId(line.getId()); +// hireEmployeeCommand.setExperience(HireEmployeeCommand.ExperienceEnum.SENIOR); +// hireEmployeeCommands.add(hireEmployeeCommand); +// }); +// request.setHireEmployeeCommands(hireEmployeeCommands); + + // готовимся закупать товар на склад и выставлять его на полки + ArrayList buyStockCommands = new ArrayList<>(); + request.setBuyStockCommands(buyStockCommands); + + ArrayList putOnRackCellCommands = new ArrayList<>(); + request.setPutOnRackCellCommands(putOnRackCellCommands); + + List stock = currentWorldResponse.getStock(); + List rackCells = currentWorldResponse.getRackCells(); + + // Обходим торговый зал и смотрим какие полки пустые. Выставляем на них товар. + currentWorldResponse.getRackCells().stream().filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)).forEach(rack -> { + Product producttoPutOnRack = null; + if (rack.getProductId() == null) { + List productsOnRack = rackCells.stream().filter(r -> r.getProductId() != null).map(RackCell::getProductId).collect(Collectors.toList()); + productsOnRack.addAll(putOnRackCellCommands.stream().map(c -> c.getProductId()).collect(Collectors.toList())); + producttoPutOnRack = stock.stream().filter(product -> !productsOnRack.contains(product.getId())).findFirst().orElse(null); + } else { + producttoPutOnRack = stock.stream().filter(product -> product.getId().equals(rack.getProductId())).findFirst().orElse(null); + } + + Integer productQuantity = rack.getProductQuantity(); + if (productQuantity == null) { + productQuantity = 0; + } + + // Вначале закупим товар на склад. Каждый ход закупать товар накладно, но ведь это тестовый игрок. + Integer orderQuantity = rack.getCapacity() - productQuantity; + if (producttoPutOnRack.getInStock() < orderQuantity) { + BuyStockCommand command = new BuyStockCommand(); + command.setProductId(producttoPutOnRack.getId()); + command.setQuantity(100); + buyStockCommands.add(command); + } + + // Далее разложим на полки. И сформируем цену. + PutOnRackCellCommand command = new PutOnRackCellCommand(); + command.setProductId(producttoPutOnRack.getId()); + command.setRackCellId(rack.getId()); + command.setProductQuantity(orderQuantity); + if (producttoPutOnRack.getSellPrice() == null) { + final int margin = 1; + command.setSellPrice(producttoPutOnRack.getStockPrice() + margin); + } + putOnRackCellCommands.add(command); + + }); + return request; + } + private void awaitServer(PerfectStoreEndpointApi psApiClient) { int awaitTimes = 60; int cnt = 0; From 0ae788147058179eee131bf5fa3ca31f27fd64ee Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Sat, 11 Sep 2021 00:17:23 +0400 Subject: [PATCH 02/12] 2nd iteration for world logging; created initial checkout line manager --- .../psplayer/PerfectStorePlayer.java | 46 +++++++++++++++---- .../model/ProductInBasket.java | 14 +++++- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index dafca24..0080257 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -20,7 +20,8 @@ public class PerfectStorePlayer implements ApplicationListener customersOnCheckline = new HashSet<>(); - private Map soldProducts = new HashMap<>(); // productId->product. product.inStock contains quantity + private Set awaitingCustomers = new HashSet<>(); + private Map soldProducts = new HashMap<>(); // productId->product public PerfectStorePlayer(@Value("${rs.endpoint:http://localhost:9080}") String serverUrl) { this.serverUrl = serverUrl; @@ -49,6 +50,7 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { if (currentWorldResponse == null) { currentWorldResponse = psApiClient.loadWorld(); + log.warn("Total employees=" + currentWorldResponse.getEmployees().size()); } CurrentTickRequest request = createNextMove(currentWorldResponse); @@ -60,9 +62,10 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { while (!currentWorldResponse.isGameOver()); // Если пришел Game Over, значит все время игры закончилось. Пора считать прибыль + log.info("Sold:" + soldProducts.values()); log.info("Я заработал " + (currentWorldResponse.getIncome() - currentWorldResponse.getSalaryCosts() - currentWorldResponse.getStockCosts()) + "руб."); log.info("Total ticks:" + cnt); - log.info("Sold products(total" + soldProducts.size() + "):" + soldProducts); + log.info("Sold products count=" + soldProducts.size()); var awaitingCheckoutCustomersCount = currentWorldResponse.getCustomers().stream(). filter(it -> it.getMode().equals(Customer.ModeEnum.WAIT_CHECKOUT)).count(); var atCheckoutCustomersCount = currentWorldResponse.getCustomers().stream(). @@ -91,10 +94,11 @@ private void collectDataFromAnswer(CurrentWorldResponse worldResponse) { var atCheckoutCustomers = customers.stream(). filter(it -> it.getMode().equals(Customer.ModeEnum.AT_CHECKOUT)). collect(Collectors.toList()); - var awaitingCheckoutProducts = awaitingCheckoutCustomers.stream(). + var newAwaitingCheckoutProducts = awaitingCheckoutCustomers.stream(). + filter(it -> !awaitingCustomers.contains(it)). map(it -> it.getBasket()). collect(ArrayList::new, List::addAll, List::addAll); - for (ProductInBasket product : awaitingCheckoutProducts) { + for (ProductInBasket product : newAwaitingCheckoutProducts) { if (soldProducts.containsKey(product.getId())) { var countedProduct = soldProducts.get(product.getId()); countedProduct.setProductCount(countedProduct.getProductCount() + product.getProductCount()); @@ -102,11 +106,12 @@ private void collectDataFromAnswer(CurrentWorldResponse worldResponse) { soldProducts.put(product.getId(), product); } } + awaitingCustomers.addAll(awaitingCheckoutCustomers); customersOnCheckline.addAll(atCheckoutCustomers); } - private CurrentTickRequest createNextMove(CurrentWorldResponse currentWorldResponse) { + private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { CurrentTickRequest request = new CurrentTickRequest(); // TODO hire only if here are too many customers @@ -114,7 +119,7 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse currentWorldRespo // request.setHireEmployeeCommands(hireEmployeeCommands); // Смотрим на каких кассах нет кассира (либо не был назначен, либо ушел с кассы отдыхать), нанимаем новых кассиров и ставим на эти кассы. // Нанимаем самых опытных! -// currentWorldResponse.getCheckoutLines().stream().filter(line -> line.getEmployeeId() == null).forEach(line -> { +// worldResponse.getCheckoutLines().stream().filter(line -> line.getEmployeeId() == null).forEach(line -> { // HireEmployeeCommand hireEmployeeCommand = new HireEmployeeCommand(); // hireEmployeeCommand.setCheckoutLineId(line.getId()); // hireEmployeeCommand.setExperience(HireEmployeeCommand.ExperienceEnum.SENIOR); @@ -122,6 +127,8 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse currentWorldRespo // }); // request.setHireEmployeeCommands(hireEmployeeCommands); + inspectChecklines(worldResponse, request); + // готовимся закупать товар на склад и выставлять его на полки ArrayList buyStockCommands = new ArrayList<>(); request.setBuyStockCommands(buyStockCommands); @@ -129,11 +136,11 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse currentWorldRespo ArrayList putOnRackCellCommands = new ArrayList<>(); request.setPutOnRackCellCommands(putOnRackCellCommands); - List stock = currentWorldResponse.getStock(); - List rackCells = currentWorldResponse.getRackCells(); + List stock = worldResponse.getStock(); + List rackCells = worldResponse.getRackCells(); // Обходим торговый зал и смотрим какие полки пустые. Выставляем на них товар. - currentWorldResponse.getRackCells().stream().filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)).forEach(rack -> { + worldResponse.getRackCells().stream().filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)).forEach(rack -> { Product producttoPutOnRack = null; if (rack.getProductId() == null) { List productsOnRack = rackCells.stream().filter(r -> r.getProductId() != null).map(RackCell::getProductId).collect(Collectors.toList()); @@ -172,6 +179,27 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse currentWorldRespo return request; } + /** + * Здесь будет вся работа по управлению кассами: слежение за свободными кассами, ротация кассиров + * + * @param worldResponse + * @param request + */ + private void inspectChecklines(CurrentWorldResponse worldResponse, CurrentTickRequest request) { + var freeEmployees = worldResponse.getEmployees(); // TODO create HR Department + freeEmployees.sort(Comparator.comparingInt(Employee::getExperience)); + + var bestEmployee = freeEmployees.isEmpty() ? null : freeEmployees.get(0); + var singleCheckline = worldResponse.getCheckoutLines().get(0); + if (singleCheckline.getEmployeeId() == null && bestEmployee != null) { + request.addSetOnCheckoutLineCommandsItem( + new SetOnCheckoutLineCommand().checkoutLineId(singleCheckline.getId()). + employeeId(bestEmployee.getId()) + ); + } + + } + private void awaitServer(PerfectStoreEndpointApi psApiClient) { int awaitTimes = 60; int cnt = 0; diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java index 9e2c663..46337fe 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java @@ -124,7 +124,7 @@ public boolean equals(java.lang.Object o) { return Objects.equals(this.id, productInBasket.id) && Objects.equals(this.productName, productInBasket.productName) && Objects.equals(this.prie, productInBasket.prie) && - Objects.equals(this.productCount, productInBasket.productCount); + Objects.equals(this.productCount, productInBasket.productCount); } @Override @@ -134,6 +134,16 @@ public int hashCode() { @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{productName=").append(productName); + sb.append(" $").append(prie); + sb.append(" Count=").append(productCount); + sb.append("} "); + return sb.toString(); + } + + /*@Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class ProductInBasket {\n"); @@ -144,7 +154,7 @@ public String toString() { sb.append(" productCount: ").append(toIndentedString(productCount)).append("\n"); sb.append("}"); return sb.toString(); - } + }*/ /** * Convert the given object to string with each line indented by 4 spaces From 7723c25308ebfd3f02013dbc78bf777ce700bdc6 Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Sat, 11 Sep 2021 02:39:59 +0400 Subject: [PATCH 03/12] introduced HR Department which assigned single employee. added more logs about world --- .../javaskills/psplayer/HRDepartment.java | 58 +++++++++++++++++++ .../psplayer/PerfectStorePlayer.java | 42 ++++++++++---- .../model/ProductInBasket.java | 5 +- 3 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java new file mode 100644 index 0000000..f5b5fba --- /dev/null +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java @@ -0,0 +1,58 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import lombok.extern.slf4j.Slf4j; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Employee; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.EmployeeRecruitmentOffer; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.HireEmployeeCommand; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Отвечает за найим и увольнение + */ +@Slf4j +public class HRDepartment { + + private List recruitmentAgency; + private List employees; + private int checkoutCount; // сколько всего касс + + public HRDepartment(List recruitmentAgency, List employees, int checkoutCount) { + this.recruitmentAgency = recruitmentAgency; + this.employees = employees; + this.checkoutCount = checkoutCount; + } + + + public List getRecruitmentAgency() { + return recruitmentAgency; + } + + public List firstHire() { + List result = new ArrayList<>(checkoutCount); + result.add(new HireEmployeeCommand() + .experience(HireEmployeeCommand.ExperienceEnum.MIDDLE) + // TODO получать реальный id + .checkoutLineId(1) + ); + log.info("Всего предложений:" + recruitmentAgency.size()); + AtomicInteger juniorsCount = new AtomicInteger(); + AtomicInteger middleCount = new AtomicInteger(); + AtomicInteger senjorCount = new AtomicInteger(); + recruitmentAgency.stream().forEach(it -> { + if (it.getEmployeeType().equalsIgnoreCase("junior")) { + juniorsCount.getAndIncrement(); + } + if (it.getEmployeeType().equalsIgnoreCase("middle")) { + middleCount.getAndIncrement(); + } + if (it.getEmployeeType().equalsIgnoreCase("senior")) { + senjorCount.getAndIncrement(); + } + }); + log.info("Джунов=" + juniorsCount + ", midlde=" + middleCount + ", senior=" + senjorCount); + return result; + } +} diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index 0080257..9c90701 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -21,7 +21,8 @@ public class PerfectStorePlayer implements ApplicationListener customersOnCheckline = new HashSet<>(); private Set awaitingCustomers = new HashSet<>(); - private Map soldProducts = new HashMap<>(); // productId->product + private Map basketProducts = new HashMap<>(); // productId->product + private HRDepartment hrDepartment; public PerfectStorePlayer(@Value("${rs.endpoint:http://localhost:9080}") String serverUrl) { this.serverUrl = serverUrl; @@ -48,13 +49,32 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { // log.info("Ответ с предыдущего шага="+currentWorldResponse); } + CurrentTickRequest request = createNextMove(currentWorldResponse); + if (currentWorldResponse == null) { currentWorldResponse = psApiClient.loadWorld(); - log.warn("Total employees=" + currentWorldResponse.getEmployees().size()); + // logging world + log.debug("Total employees=" + currentWorldResponse.getEmployees().size()); + log.info("Касс=" + currentWorldResponse.getCheckoutLines().size()); + log.info("Полок=" + currentWorldResponse.getRackCells().size()); + var productAssortment = currentWorldResponse.getStock().size(); + var productTotalCount = currentWorldResponse.getStock(). + stream().mapToInt(Product::getInStock).summaryStatistics(); + log.info("Видов товаров=" + productAssortment + ", штук=" + productTotalCount + + ", стоит=" + currentWorldResponse.getStockCosts()); + log.info(currentWorldResponse.getCheckoutLines().get(0).toString()); + // end of logging + + // generate initial commands + hrDepartment = new HRDepartment(currentWorldResponse.getRecruitmentAgency(), + currentWorldResponse.getEmployees(), + currentWorldResponse.getCheckoutLines().size() + ); + request = new CurrentTickRequest(); + request.hireEmployeeCommands(hrDepartment.firstHire()); + // TODO init racks by products } - CurrentTickRequest request = createNextMove(currentWorldResponse); - currentWorldResponse = psApiClient.tick(request); collectDataFromAnswer(currentWorldResponse); @@ -62,10 +82,9 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { while (!currentWorldResponse.isGameOver()); // Если пришел Game Over, значит все время игры закончилось. Пора считать прибыль - log.info("Sold:" + soldProducts.values()); + log.info("В корзинах:" + basketProducts.values()); log.info("Я заработал " + (currentWorldResponse.getIncome() - currentWorldResponse.getSalaryCosts() - currentWorldResponse.getStockCosts()) + "руб."); - log.info("Total ticks:" + cnt); - log.info("Sold products count=" + soldProducts.size()); + log.info("Sold products count=" + basketProducts.size()); var awaitingCheckoutCustomersCount = currentWorldResponse.getCustomers().stream(). filter(it -> it.getMode().equals(Customer.ModeEnum.WAIT_CHECKOUT)).count(); var atCheckoutCustomersCount = currentWorldResponse.getCustomers().stream(). @@ -99,11 +118,11 @@ private void collectDataFromAnswer(CurrentWorldResponse worldResponse) { map(it -> it.getBasket()). collect(ArrayList::new, List::addAll, List::addAll); for (ProductInBasket product : newAwaitingCheckoutProducts) { - if (soldProducts.containsKey(product.getId())) { - var countedProduct = soldProducts.get(product.getId()); + if (basketProducts.containsKey(product.getId())) { + var countedProduct = basketProducts.get(product.getId()); countedProduct.setProductCount(countedProduct.getProductCount() + product.getProductCount()); } else { - soldProducts.put(product.getId(), product); + basketProducts.put(product.getId(), product); } } awaitingCustomers.addAll(awaitingCheckoutCustomers); @@ -112,6 +131,9 @@ private void collectDataFromAnswer(CurrentWorldResponse worldResponse) { } private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { + if (worldResponse == null) { // первый ход обрабатывается отдельно + return null; + } CurrentTickRequest request = new CurrentTickRequest(); // TODO hire only if here are too many customers diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java index 46337fe..b9b4e2d 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java @@ -136,10 +136,11 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("{productName=").append(productName); + sb.append("product{").append(id).append("}"); + sb.append(" ").append(productName); sb.append(" $").append(prie); sb.append(" Count=").append(productCount); - sb.append("} "); + sb.append(", "); return sb.toString(); } From c9e5197ccf39c323cadaa72e98cc934617c675a0 Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Sat, 11 Sep 2021 21:03:37 +0400 Subject: [PATCH 04/12] introduced CheckoutAdmin, implemented strategy '3 employeees at single checkout' --- .../javaskills/psplayer/CheckoutAdmin.java | 65 +++++++++++++ .../psplayer/EmployeeWorkTable.java | 73 ++++++++++++++ .../javaskills/psplayer/HRDepartment.java | 17 +--- .../javaskills/psplayer/Merchandaizer.java | 22 +++++ .../psplayer/PerfectStorePlayer.java | 96 ++++++++++--------- .../model/EmployeeRecruitmentOffer.java | 21 ++-- 6 files changed, 224 insertions(+), 70 deletions(-) create mode 100644 src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java create mode 100644 src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java create mode 100644 src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java new file mode 100644 index 0000000..73ed368 --- /dev/null +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java @@ -0,0 +1,65 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import lombok.extern.slf4j.Slf4j; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.CheckoutLine; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Employee; + +import java.util.ArrayList; +import java.util.List; + +/** + * Управляет кассирами на кассах: следит, кто когда ушел отдыхать, + * когда может работать снова, кого именно сажать на кассу. + * Используется в стратегии "не увольнять сразу" + */ +@Slf4j +public class CheckoutAdmin { + + private List employees; + private List timesheet; + + public CheckoutAdmin(List employees) { + this.employees = employees; + init(); + } + + private void init() { + if (employees == null || employees.isEmpty()) { + timesheet = new ArrayList<>(); + return; + } + timesheet = new ArrayList<>(employees.size()); + employees.forEach(it -> timesheet.add(new EmployeeWorkTable(it))); + } + + public Employee sendToWorkAny(int currentTime) { + var candidate = timesheet.stream() + .filter(it -> it.isReadyToWork(currentTime)) + .findAny().orElse(null); + if (candidate == null) { + log.warn("Not enough employees to work!"); + return null; + } + candidate.startWork(currentTime); + return candidate.employee; + } + + // TODO add tests + // FIXME учесть уволенных + public void considerNew(List employeeList, + List checkoutLines, + Integer tickCount) { + var newWorkers = new ArrayList<>(employeeList); + newWorkers.removeAll(employees); + employees.addAll(newWorkers); + for (Employee newbie : newWorkers) { + var alreadyAssigned = checkoutLines.stream() + .anyMatch(line -> newbie.getId().equals(line.getEmployeeId())); + final EmployeeWorkTable newbieWorkTable = new EmployeeWorkTable(newbie); + if (alreadyAssigned) { + newbieWorkTable.startWork(tickCount); + } + timesheet.add(newbieWorkTable); + } + } +} diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java new file mode 100644 index 0000000..a6d9ec7 --- /dev/null +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java @@ -0,0 +1,73 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Employee; + +class EmployeeWorkTable { + private final static int HOUR_IN_TICKS = 60; + private final static int WORK_SHIFT_IN_TICKS = 8 * HOUR_IN_TICKS; + private final static int REST_TIME_IN_TICKS = 16 * WORK_SHIFT_IN_TICKS; + + @NonNull + Employee employee; + Integer startedWork = null; + + public EmployeeWorkTable(Employee employee) { + this.employee = employee; + } + + // probably change return type + public boolean startWork(int currentTime) { + if (isReadyToWork(currentTime)) { + startedWork = currentTime; + return true; + } + return false; + } + + /** + * @return time (in ticks) when this employee started his last work shift. + * Null if it never works. + */ + @Nullable + public Integer getStartedWork() { + return startedWork; + } + + @NonNull + public Employee getEmployee() { + return employee; + } + + /** + * @return time (in ticks) when this employee will finish his work shift. + * Null if it never works + */ + @Nullable + public Integer getFinishedWork() { + if (startedWork == null) { + return null; + } + return startedWork + WORK_SHIFT_IN_TICKS; + } + + /** + * @return time (in ticks) when this employee could start to work. + * Null if it never works and could start immediately + */ + @Nullable + public Integer getReadyToWork() { + if (startedWork == null) { + return null; + } + return getFinishedWork() + REST_TIME_IN_TICKS; + } + + public boolean isReadyToWork(int currentTime) { + if (startedWork == null) { + return true; + } + return getReadyToWork() < currentTime; + } +} diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java index f5b5fba..043f255 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java @@ -7,7 +7,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; /** * Отвечает за найим и увольнение @@ -38,21 +37,7 @@ public List firstHire() { .checkoutLineId(1) ); log.info("Всего предложений:" + recruitmentAgency.size()); - AtomicInteger juniorsCount = new AtomicInteger(); - AtomicInteger middleCount = new AtomicInteger(); - AtomicInteger senjorCount = new AtomicInteger(); - recruitmentAgency.stream().forEach(it -> { - if (it.getEmployeeType().equalsIgnoreCase("junior")) { - juniorsCount.getAndIncrement(); - } - if (it.getEmployeeType().equalsIgnoreCase("middle")) { - middleCount.getAndIncrement(); - } - if (it.getEmployeeType().equalsIgnoreCase("senior")) { - senjorCount.getAndIncrement(); - } - }); - log.info("Джунов=" + juniorsCount + ", midlde=" + middleCount + ", senior=" + senjorCount); + log.info(recruitmentAgency.toString()); return result; } } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java new file mode 100644 index 0000000..aaa5ab6 --- /dev/null +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java @@ -0,0 +1,22 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Product; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.RackCell; + +import java.util.Comparator; +import java.util.List; + +/** + * Выкладывает товар на полки, определяет наценки. + * Будет ли заниматься заказом товара - пока не знаю + */ +public class Merchandaizer { + private List racks; + private List stock; + + public Merchandaizer(List racks, List stock) { + this.racks = racks; + this.stock = stock; + this.stock.sort(Comparator.comparing(Product::getStockPrice)); + } +} diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index 9c90701..23d3888 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -23,6 +23,7 @@ public class PerfectStorePlayer implements ApplicationListener awaitingCustomers = new HashSet<>(); private Map basketProducts = new HashMap<>(); // productId->product private HRDepartment hrDepartment; + private CheckoutAdmin checkoutAdmin; public PerfectStorePlayer(@Value("${rs.endpoint:http://localhost:9080}") String serverUrl) { this.serverUrl = serverUrl; @@ -35,7 +36,7 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { PerfectStoreEndpointApi psApiClient = new PerfectStoreEndpointApi(apiClient); - log.info("Игрок готов. Подключаемся к серверу.."); + log.debug("Игрок готов. Подключаемся к серверу.."); awaitServer(psApiClient); log.info("Подключение к серверу успешно. Начинаем игру"); @@ -55,12 +56,13 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { currentWorldResponse = psApiClient.loadWorld(); // logging world log.debug("Total employees=" + currentWorldResponse.getEmployees().size()); - log.info("Касс=" + currentWorldResponse.getCheckoutLines().size()); + log.info("Кассы" + currentWorldResponse.getCheckoutLines()); log.info("Полок=" + currentWorldResponse.getRackCells().size()); var productAssortment = currentWorldResponse.getStock().size(); - var productTotalCount = currentWorldResponse.getStock(). - stream().mapToInt(Product::getInStock).summaryStatistics(); - log.info("Видов товаров=" + productAssortment + ", штук=" + productTotalCount + + var productCountList = currentWorldResponse.getStock(). + stream().sorted(Comparator.comparingDouble(Product::getStockPrice)) + .map(Product::getInStock).collect(Collectors.toList()); + log.info("Видов товаров=" + productAssortment + ", штук=" + productCountList + ", стоит=" + currentWorldResponse.getStockCosts()); log.info(currentWorldResponse.getCheckoutLines().get(0).toString()); // end of logging @@ -70,6 +72,8 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { currentWorldResponse.getEmployees(), currentWorldResponse.getCheckoutLines().size() ); + checkoutAdmin = new CheckoutAdmin(currentWorldResponse.getEmployees()); + request = new CurrentTickRequest(); request.hireEmployeeCommands(hrDepartment.firstHire()); // TODO init racks by products @@ -136,19 +140,9 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { } CurrentTickRequest request = new CurrentTickRequest(); - // TODO hire only if here are too many customers -// List hireEmployeeCommands = new ArrayList<>(); -// request.setHireEmployeeCommands(hireEmployeeCommands); - // Смотрим на каких кассах нет кассира (либо не был назначен, либо ушел с кассы отдыхать), нанимаем новых кассиров и ставим на эти кассы. - // Нанимаем самых опытных! -// worldResponse.getCheckoutLines().stream().filter(line -> line.getEmployeeId() == null).forEach(line -> { -// HireEmployeeCommand hireEmployeeCommand = new HireEmployeeCommand(); -// hireEmployeeCommand.setCheckoutLineId(line.getId()); -// hireEmployeeCommand.setExperience(HireEmployeeCommand.ExperienceEnum.SENIOR); -// hireEmployeeCommands.add(hireEmployeeCommand); -// }); -// request.setHireEmployeeCommands(hireEmployeeCommands); - + checkoutAdmin.considerNew(worldResponse.getEmployees(), + worldResponse.getCheckoutLines(), + worldResponse.getTickCount()); inspectChecklines(worldResponse, request); // готовимся закупать товар на склад и выставлять его на полки @@ -162,29 +156,39 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { List rackCells = worldResponse.getRackCells(); // Обходим торговый зал и смотрим какие полки пустые. Выставляем на них товар. - worldResponse.getRackCells().stream().filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)).forEach(rack -> { - Product producttoPutOnRack = null; - if (rack.getProductId() == null) { - List productsOnRack = rackCells.stream().filter(r -> r.getProductId() != null).map(RackCell::getProductId).collect(Collectors.toList()); - productsOnRack.addAll(putOnRackCellCommands.stream().map(c -> c.getProductId()).collect(Collectors.toList())); - producttoPutOnRack = stock.stream().filter(product -> !productsOnRack.contains(product.getId())).findFirst().orElse(null); - } else { - producttoPutOnRack = stock.stream().filter(product -> product.getId().equals(rack.getProductId())).findFirst().orElse(null); - } - - Integer productQuantity = rack.getProductQuantity(); - if (productQuantity == null) { - productQuantity = 0; - } - - // Вначале закупим товар на склад. Каждый ход закупать товар накладно, но ведь это тестовый игрок. - Integer orderQuantity = rack.getCapacity() - productQuantity; - if (producttoPutOnRack.getInStock() < orderQuantity) { + worldResponse.getRackCells().stream() + .filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)) + .forEach(rack -> { + Product producttoPutOnRack = null; + if (rack.getProductId() == null) { + List productsOnRack = rackCells.stream().filter(r -> r.getProductId() != null).map(RackCell::getProductId).collect(Collectors.toList()); + productsOnRack.addAll(putOnRackCellCommands.stream().map(c -> c.getProductId()).collect(Collectors.toList())); + producttoPutOnRack = stock.stream() + .filter(product -> !productsOnRack.contains(product.getId())) + .findFirst().orElse(null); + } else { + producttoPutOnRack = stock.stream() + .filter(product -> product.getId().equals(rack.getProductId())) + .findFirst().orElse(null); + } + if (producttoPutOnRack == null) { + producttoPutOnRack = stock.stream().filter(product -> product.getInStock() > 0) + .findFirst().orElse(null); + } + + Integer productQuantity = rack.getProductQuantity(); + if (productQuantity == null) { + productQuantity = 0; + } + + // Вначале закупим товар на склад. Каждый ход закупать товар накладно, но ведь это тестовый игрок. + Integer orderQuantity = rack.getCapacity() - productQuantity; + /*if (producttoPutOnRack.getInStock() < orderQuantity) { BuyStockCommand command = new BuyStockCommand(); command.setProductId(producttoPutOnRack.getId()); command.setQuantity(100); buyStockCommands.add(command); - } + }*/ // Далее разложим на полки. И сформируем цену. PutOnRackCellCommand command = new PutOnRackCellCommand(); @@ -208,16 +212,16 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { * @param request */ private void inspectChecklines(CurrentWorldResponse worldResponse, CurrentTickRequest request) { - var freeEmployees = worldResponse.getEmployees(); // TODO create HR Department - freeEmployees.sort(Comparator.comparingInt(Employee::getExperience)); - - var bestEmployee = freeEmployees.isEmpty() ? null : freeEmployees.get(0); + // Стратегия "одна работающая касса", кассиры не увольняются var singleCheckline = worldResponse.getCheckoutLines().get(0); - if (singleCheckline.getEmployeeId() == null && bestEmployee != null) { - request.addSetOnCheckoutLineCommandsItem( - new SetOnCheckoutLineCommand().checkoutLineId(singleCheckline.getId()). - employeeId(bestEmployee.getId()) - ); + if (singleCheckline.getEmployeeId() == null) { + var employee = checkoutAdmin.sendToWorkAny(worldResponse.getCurrentTick()); + if (employee != null) { + request.addSetOnCheckoutLineCommandsItem( + new SetOnCheckoutLineCommand().checkoutLineId(singleCheckline.getId()). + employeeId(employee.getId()) + ); + } } } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/EmployeeRecruitmentOffer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/EmployeeRecruitmentOffer.java index 14eca47..c9c70bf 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/EmployeeRecruitmentOffer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/EmployeeRecruitmentOffer.java @@ -12,15 +12,10 @@ package ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model; -import java.util.Objects; -import java.util.Arrays; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import io.swagger.v3.oas.annotations.media.Schema; -import java.io.IOException; + +import java.util.Objects; /** * Кадровое агенство. Справочная информация о том, каких сотрудников можно нанять и по какой ставке */ @@ -112,6 +107,16 @@ public int hashCode() { @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Type=").append((employeeType)); + sb.append(", experience: ").append((experience)); + sb.append(", salary: ").append((salary)); + sb.append("}"); + return sb.toString(); + } + + /*@Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class EmployeeRecruitmentOffer {\n"); @@ -121,7 +126,7 @@ public String toString() { sb.append(" salary: ").append(toIndentedString(salary)).append("\n"); sb.append("}"); return sb.toString(); - } + }*/ /** * Convert the given object to string with each line indented by 4 spaces From f930ede34173a0934a10d322ea518f23e6f0f277 Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Sat, 11 Sep 2021 21:45:15 +0400 Subject: [PATCH 05/12] hire 3 employees. added logging --- .../javaskills/psplayer/CheckoutAdmin.java | 1 + .../javaskills/psplayer/EmployeeWorkTable.java | 10 ++++++++++ .../javaskills/psplayer/HRDepartment.java | 18 ++++++++++++------ .../psplayer/PerfectStorePlayer.java | 4 ++-- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java index 73ed368..1691ef9 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java @@ -60,6 +60,7 @@ public void considerNew(List employeeList, newbieWorkTable.startWork(tickCount); } timesheet.add(newbieWorkTable); + log.info(newbieWorkTable.toString()); } } } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java index a6d9ec7..afcee56 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java @@ -70,4 +70,14 @@ public boolean isReadyToWork(int currentTime) { } return getReadyToWork() < currentTime; } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("EmployeeWorkTable{"); + sb.append("employee(").append(employee.getId()).append(")"); + sb.append(" ").append(employee.getFirstName()); + sb.append(", started=").append(startedWork); + sb.append('}'); + return sb.toString(); + } } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java index 043f255..92dd32e 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java @@ -30,14 +30,20 @@ public List getRecruitmentAgency() { } public List firstHire() { - List result = new ArrayList<>(checkoutCount); - result.add(new HireEmployeeCommand() - .experience(HireEmployeeCommand.ExperienceEnum.MIDDLE) - // TODO получать реальный id - .checkoutLineId(1) - ); + List result = new ArrayList<>(recruitmentAgency.size()); + for (int i = 0; i < 3 && i < recruitmentAgency.size(); i++) { + var offer = recruitmentAgency.get(i); + result.add(new HireEmployeeCommand() + .experience(HireEmployeeCommand.ExperienceEnum.fromValue(offer.getEmployeeType())) + + ); + + } + // TODO получать реальный id + result.get(0).checkoutLineId(1); log.info("Всего предложений:" + recruitmentAgency.size()); log.info(recruitmentAgency.toString()); + log.info(result.toString()); return result; } } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index 23d3888..1cb6e99 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -64,16 +64,16 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { .map(Product::getInStock).collect(Collectors.toList()); log.info("Видов товаров=" + productAssortment + ", штук=" + productCountList + ", стоит=" + currentWorldResponse.getStockCosts()); - log.info(currentWorldResponse.getCheckoutLines().get(0).toString()); // end of logging - // generate initial commands + // init state hrDepartment = new HRDepartment(currentWorldResponse.getRecruitmentAgency(), currentWorldResponse.getEmployees(), currentWorldResponse.getCheckoutLines().size() ); checkoutAdmin = new CheckoutAdmin(currentWorldResponse.getEmployees()); + // generate initial commands request = new CurrentTickRequest(); request.hireEmployeeCommands(hrDepartment.firstHire()); // TODO init racks by products From 83a0b1715c5ab8e1a3ee8a8287bb81d247c969bb Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Sun, 12 Sep 2021 21:19:20 +0400 Subject: [PATCH 06/12] fixed bug when 3 employees can't fill 24 hours --- .../javaskills/psplayer/CheckoutAdmin.java | 19 +++- .../psplayer/EmployeeWorkTable.java | 22 ++++- .../psplayer/PerfectStorePlayer.java | 20 ++-- .../psplayer/CheckoutAdminTest.java | 93 +++++++++++++++++++ .../PerfectStorePlayerApplicationTests.java | 4 +- 5 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 src/test/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdminTest.java diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java index 1691ef9..7c47b05 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java @@ -17,9 +17,10 @@ public class CheckoutAdmin { private List employees; private List timesheet; + private boolean notFailed = false; public CheckoutAdmin(List employees) { - this.employees = employees; + this.employees = new ArrayList<>(employees); init(); } @@ -37,11 +38,15 @@ public Employee sendToWorkAny(int currentTime) { .filter(it -> it.isReadyToWork(currentTime)) .findAny().orElse(null); if (candidate == null) { - log.warn("Not enough employees to work!"); + if (notFailed || currentTime % 120 == 0) { + log.warn("Not enough employees to work at=" + currentTime + ", " + timesheet); + } + notFailed = false; return null; } + notFailed = true; candidate.startWork(currentTime); - return candidate.employee; + return candidate.getEmployee(); } // TODO add tests @@ -63,4 +68,12 @@ public void considerNew(List employeeList, log.info(newbieWorkTable.toString()); } } + + public List getEmployees() { + return employees; + } + + public List getTimesheet() { + return timesheet; + } } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java index afcee56..145d538 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java @@ -7,11 +7,11 @@ class EmployeeWorkTable { private final static int HOUR_IN_TICKS = 60; private final static int WORK_SHIFT_IN_TICKS = 8 * HOUR_IN_TICKS; - private final static int REST_TIME_IN_TICKS = 16 * WORK_SHIFT_IN_TICKS; + private final static int REST_TIME_IN_TICKS = 16 * HOUR_IN_TICKS; @NonNull - Employee employee; - Integer startedWork = null; + private Employee employee; + private Integer startedWork = null; public EmployeeWorkTable(Employee employee) { this.employee = employee; @@ -68,7 +68,19 @@ public boolean isReadyToWork(int currentTime) { if (startedWork == null) { return true; } - return getReadyToWork() < currentTime; + return getReadyToWork() <= currentTime; + } + + public static int getWorkShiftInTicks() { + return WORK_SHIFT_IN_TICKS; + } + + public static int getRestTimeInTicks() { + return REST_TIME_IN_TICKS; + } + + public static int getHourInTicks() { + return HOUR_IN_TICKS; } @Override @@ -76,6 +88,8 @@ public String toString() { final StringBuffer sb = new StringBuffer("EmployeeWorkTable{"); sb.append("employee(").append(employee.getId()).append(")"); sb.append(" ").append(employee.getFirstName()); + sb.append(", exp=").append(employee.getExperience()); + sb.append(", $").append(employee.getSalary()); sb.append(", started=").append(startedWork); sb.append('}'); return sb.toString(); diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index 1cb6e99..c2d0a76 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -56,13 +56,13 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { currentWorldResponse = psApiClient.loadWorld(); // logging world log.debug("Total employees=" + currentWorldResponse.getEmployees().size()); - log.info("Кассы" + currentWorldResponse.getCheckoutLines()); - log.info("Полок=" + currentWorldResponse.getRackCells().size()); + log.debug("Кассы" + currentWorldResponse.getCheckoutLines()); + log.debug("Полок=" + currentWorldResponse.getRackCells().size()); var productAssortment = currentWorldResponse.getStock().size(); var productCountList = currentWorldResponse.getStock(). stream().sorted(Comparator.comparingDouble(Product::getStockPrice)) .map(Product::getInStock).collect(Collectors.toList()); - log.info("Видов товаров=" + productAssortment + ", штук=" + productCountList + + log.debug("Видов товаров=" + productAssortment + ", штук=" + productCountList + ", стоит=" + currentWorldResponse.getStockCosts()); // end of logging @@ -142,7 +142,7 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { checkoutAdmin.considerNew(worldResponse.getEmployees(), worldResponse.getCheckoutLines(), - worldResponse.getTickCount()); + worldResponse.getCurrentTick()); inspectChecklines(worldResponse, request); // готовимся закупать товар на склад и выставлять его на полки @@ -183,12 +183,12 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { // Вначале закупим товар на склад. Каждый ход закупать товар накладно, но ведь это тестовый игрок. Integer orderQuantity = rack.getCapacity() - productQuantity; - /*if (producttoPutOnRack.getInStock() < orderQuantity) { - BuyStockCommand command = new BuyStockCommand(); - command.setProductId(producttoPutOnRack.getId()); - command.setQuantity(100); - buyStockCommands.add(command); - }*/ + if (producttoPutOnRack.getInStock() < orderQuantity) { + BuyStockCommand command = new BuyStockCommand(); + command.setProductId(producttoPutOnRack.getId()); + command.setQuantity(100); + buyStockCommands.add(command); + } // Далее разложим на полки. И сформируем цену. PutOnRackCellCommand command = new PutOnRackCellCommand(); diff --git a/src/test/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdminTest.java b/src/test/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdminTest.java new file mode 100644 index 0000000..e27a468 --- /dev/null +++ b/src/test/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdminTest.java @@ -0,0 +1,93 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.CheckoutLine; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Employee; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static ru.hilariousstartups.javaskills.psplayer.EmployeeWorkTable.getRestTimeInTicks; +import static ru.hilariousstartups.javaskills.psplayer.EmployeeWorkTable.getWorkShiftInTicks; + +public class CheckoutAdminTest { + + private final static List EMPLOYEE_LIST = Arrays.asList( + new Employee().id(1).experience(10).firstName("E1").salary(100), + new Employee().id(2).experience(40).firstName("E2").salary(300), + new Employee().id(3).experience(70).firstName("E3").salary(560) + + ); + + private CheckoutAdmin service = new CheckoutAdmin(EMPLOYEE_LIST); + + @BeforeEach + public void setup() { + System.out.println("---next test------"); + } + + @Test + public void addNewEmployee() { + var newbie = new Employee().id(10).experience(15).firstName("new").salary(250); + var employees = new ArrayList<>(EMPLOYEE_LIST); + employees.add(newbie); + service.considerNew(employees, Collections.emptyList(), 1); + final int elementsCount = employees.size(); + assertEquals(elementsCount, service.getEmployees().size()); + assertEquals(elementsCount, service.getTimesheet().size()); + final EmployeeWorkTable actual = service.getTimesheet().get(elementsCount - 1); + assertEquals(newbie, actual.getEmployee()); + assertNull(actual.getStartedWork()); + assertNull(actual.getFinishedWork()); + assertNull(actual.getReadyToWork()); + } + + @Test + public void addWorkingEmployee() { + final int employeeId = 10; + var newbie = new Employee().id(employeeId).experience(15) + .firstName("new").salary(250); + var employees = new ArrayList<>(EMPLOYEE_LIST); + employees.add(newbie); + List checkouts = Arrays.asList(new CheckoutLine().employeeId(employeeId)); + final int hireTime = 1; + service.considerNew(employees, checkouts, hireTime); + final int elementsCount = employees.size(); + assertEquals(elementsCount, service.getEmployees().size()); + assertEquals(elementsCount, service.getTimesheet().size()); + final EmployeeWorkTable actual = service.getTimesheet().get(elementsCount - 1); + assertEquals(newbie, actual.getEmployee()); + assertEquals(hireTime, actual.getStartedWork()); + assertEquals(hireTime + getWorkShiftInTicks(), actual.getFinishedWork()); + assertEquals(hireTime + getWorkShiftInTicks() + getRestTimeInTicks(), + actual.getReadyToWork()); + } + + @Test + public void workersPerDay() { + int minutesInDay = 60 * 24; + assertEquals(minutesInDay, getWorkShiftInTicks() + getRestTimeInTicks()); + } + + @Test + void assignNextEmployeeToWork() { + final int firstShift = 2; + final int secondShift = firstShift + getWorkShiftInTicks(); + final int thirdShift = firstShift + 2 * getWorkShiftInTicks(); + final int fourthShift = firstShift + 3 * getWorkShiftInTicks(); + var first = service.sendToWorkAny(firstShift); + System.out.println("after first " + service.getTimesheet()); + var second = service.sendToWorkAny(secondShift); + System.out.println("after second = " + service.getTimesheet()); + var third = service.sendToWorkAny(thirdShift); + System.out.println("after third = " + service.getTimesheet()); + + var fourth = service.sendToWorkAny(fourthShift); + assertEquals(first, fourth); + } +} diff --git a/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java b/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java index 3591699..89bb476 100644 --- a/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java +++ b/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java @@ -3,10 +3,10 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -//@SpringBootTest +@SpringBootTest class PerfectStorePlayerApplicationTests { -// @Test + @Test void contextLoads() { } From 162540e2abe32ffd413a77f0e88eb38d06129677 Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Sun, 12 Sep 2021 23:28:53 +0400 Subject: [PATCH 07/12] introduced Merchandaizer --- .../javaskills/psplayer/Merchandaizer.java | 68 +++++++++++++++++-- .../psplayer/PerfectStorePlayer.java | 44 +++++++----- .../swagger_codegen/model/Product.java | 23 +++---- .../swagger_codegen/model/RackCell.java | 30 +++----- 4 files changed, 109 insertions(+), 56 deletions(-) diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java index aaa5ab6..a4121d3 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java @@ -1,22 +1,82 @@ package ru.hilariousstartups.javaskills.psplayer; -import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Product; -import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.RackCell; +import lombok.extern.slf4j.Slf4j; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.*; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; /** * Выкладывает товар на полки, определяет наценки. * Будет ли заниматься заказом товара - пока не знаю */ +@Slf4j public class Merchandaizer { + private static final Integer DEFAULT_QUANTITY = 500; private List racks; private List stock; + private List productsOnRack; public Merchandaizer(List racks, List stock) { - this.racks = racks; - this.stock = stock; + this.racks = new ArrayList<>(racks); + this.stock = new ArrayList<>(stock); + log.info(stock.toString()); this.stock.sort(Comparator.comparing(Product::getStockPrice)); } + + public List inspectRacks(CurrentWorldResponse world) { + + racks = world.getRackCells(); + stock = world.getStock(); + stock.sort(Comparator.comparing(Product::getStockPrice)); + productsOnRack = racks.stream() + .filter(r -> r.getProductId() != null) + .map(RackCell::getProductId) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + + racks.stream() + .filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)) + .forEach(rack -> { + final PutOnRackCellCommand command = putNextFromStock(rack); + result.add(command); + }); + + return result; + } + + public List inspectStore() { + List result = new ArrayList<>(); + stock.stream().filter(it -> it.getInStock().equals(0)) + .forEach(product -> result.add(new BuyStockCommand() + .productId(product.getId()) + .quantity(DEFAULT_QUANTITY) + )); + return result; + } + + private PutOnRackCellCommand putNextFromStock(RackCell rack) { + Product productToPutOnRack; + productToPutOnRack = stock.stream() + .filter(product -> !productsOnRack.contains(product.getId()) + && product.getInStock() > 0) + .findFirst().orElse(null); + productsOnRack.add(productToPutOnRack.getId()); + final Integer inStock = productToPutOnRack.getInStock(); + int rackCapacity = rack.getCapacity(); + return new PutOnRackCellCommand().productId(productToPutOnRack.getId()) + .rackCellId(rack.getId()) + .productQuantity(Math.min(inStock, rackCapacity)); + } + + public List initialBuyIn() { + List buyStockCommands = new ArrayList<>(50); + stock.forEach(it -> buyStockCommands.add( + new BuyStockCommand().productId(it.getId()).quantity(DEFAULT_QUANTITY) + )); + return buyStockCommands; + } } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index c2d0a76..f1f0d04 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -24,6 +24,7 @@ public class PerfectStorePlayer implements ApplicationListener basketProducts = new HashMap<>(); // productId->product private HRDepartment hrDepartment; private CheckoutAdmin checkoutAdmin; + private Merchandaizer merch; public PerfectStorePlayer(@Value("${rs.endpoint:http://localhost:9080}") String serverUrl) { this.serverUrl = serverUrl; @@ -45,7 +46,7 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { int cnt = 0; do { cnt += 1; - if (cnt % 120 == 0) { + if (cnt % 60 * 4 == 0) { log.info("Пройден " + cnt + " тик"); // log.info("Ответ с предыдущего шага="+currentWorldResponse); } @@ -57,13 +58,14 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { // logging world log.debug("Total employees=" + currentWorldResponse.getEmployees().size()); log.debug("Кассы" + currentWorldResponse.getCheckoutLines()); - log.debug("Полок=" + currentWorldResponse.getRackCells().size()); var productAssortment = currentWorldResponse.getStock().size(); var productCountList = currentWorldResponse.getStock(). stream().sorted(Comparator.comparingDouble(Product::getStockPrice)) .map(Product::getInStock).collect(Collectors.toList()); log.debug("Видов товаров=" + productAssortment + ", штук=" + productCountList + ", стоит=" + currentWorldResponse.getStockCosts()); + log.debug("Полок=" + currentWorldResponse.getRackCells().size()); + log.info(currentWorldResponse.getRackCells().toString()); // end of logging // init state @@ -72,11 +74,13 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { currentWorldResponse.getCheckoutLines().size() ); checkoutAdmin = new CheckoutAdmin(currentWorldResponse.getEmployees()); + merch = new Merchandaizer(currentWorldResponse.getRackCells(), + currentWorldResponse.getStock()); // generate initial commands request = new CurrentTickRequest(); request.hireEmployeeCommands(hrDepartment.firstHire()); - // TODO init racks by products + request.buyStockCommands(merch.initialBuyIn()); } currentWorldResponse = psApiClient.tick(request); @@ -146,33 +150,37 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { inspectChecklines(worldResponse, request); // готовимся закупать товар на склад и выставлять его на полки - ArrayList buyStockCommands = new ArrayList<>(); + final List putOnRackCellCommands = merch.inspectRacks(worldResponse); + request.setPutOnRackCellCommands(putOnRackCellCommands); + final List buyStockCommands = merch.inspectStore(); + request.buyStockCommands(buyStockCommands); + /*ArrayList buyStockCommands = new ArrayList<>(); request.setBuyStockCommands(buyStockCommands); ArrayList putOnRackCellCommands = new ArrayList<>(); - request.setPutOnRackCellCommands(putOnRackCellCommands); + List stock = worldResponse.getStock(); List rackCells = worldResponse.getRackCells(); // Обходим торговый зал и смотрим какие полки пустые. Выставляем на них товар. - worldResponse.getRackCells().stream() + /*worldResponse.getRackCells().stream() .filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)) .forEach(rack -> { - Product producttoPutOnRack = null; + Product productToPutOnRack = null; if (rack.getProductId() == null) { List productsOnRack = rackCells.stream().filter(r -> r.getProductId() != null).map(RackCell::getProductId).collect(Collectors.toList()); productsOnRack.addAll(putOnRackCellCommands.stream().map(c -> c.getProductId()).collect(Collectors.toList())); - producttoPutOnRack = stock.stream() + productToPutOnRack = stock.stream() .filter(product -> !productsOnRack.contains(product.getId())) .findFirst().orElse(null); } else { - producttoPutOnRack = stock.stream() + productToPutOnRack = stock.stream() .filter(product -> product.getId().equals(rack.getProductId())) .findFirst().orElse(null); } - if (producttoPutOnRack == null) { - producttoPutOnRack = stock.stream().filter(product -> product.getInStock() > 0) + if (productToPutOnRack == null) { + productToPutOnRack = stock.stream().filter(product -> product.getInStock() > 0) .findFirst().orElse(null); } @@ -183,25 +191,25 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { // Вначале закупим товар на склад. Каждый ход закупать товар накладно, но ведь это тестовый игрок. Integer orderQuantity = rack.getCapacity() - productQuantity; - if (producttoPutOnRack.getInStock() < orderQuantity) { + if (productToPutOnRack.getInStock() < orderQuantity) { BuyStockCommand command = new BuyStockCommand(); - command.setProductId(producttoPutOnRack.getId()); + command.setProductId(productToPutOnRack.getId()); command.setQuantity(100); buyStockCommands.add(command); } // Далее разложим на полки. И сформируем цену. PutOnRackCellCommand command = new PutOnRackCellCommand(); - command.setProductId(producttoPutOnRack.getId()); + command.setProductId(productToPutOnRack.getId()); command.setRackCellId(rack.getId()); command.setProductQuantity(orderQuantity); - if (producttoPutOnRack.getSellPrice() == null) { - final int margin = 1; - command.setSellPrice(producttoPutOnRack.getStockPrice() + margin); + if (productToPutOnRack.getSellPrice() == null) { + final int margin = (int)Math.round(productToPutOnRack.getStockPrice()*0.1); + command.setSellPrice(productToPutOnRack.getStockPrice() + margin); } putOnRackCellCommands.add(command); - }); + });*/ return request; } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java index d0de3e3..591de9f 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java @@ -12,15 +12,10 @@ package ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model; -import java.util.Objects; -import java.util.Arrays; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import io.swagger.v3.oas.annotations.media.Schema; -import java.io.IOException; + +import java.util.Objects; /** * Продукт (товар) находится либо на складе либо может быть выложен на полку в магазине. Один продукт может лежать только на одной полке. Продукт на склад можно докупить по закупочной цене. И выставить на полку по любой цене. Чем дороже цена, тем сложнее покупателям купить товар. */ @@ -158,14 +153,12 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("class Product {\n"); - - sb.append(" id: ").append(toIndentedString(id)).append("\n"); - sb.append(" name: ").append(toIndentedString(name)).append("\n"); - sb.append(" stockPrice: ").append(toIndentedString(stockPrice)).append("\n"); - sb.append(" inStock: ").append(toIndentedString(inStock)).append("\n"); - sb.append(" sellPrice: ").append(toIndentedString(sellPrice)).append("\n"); - sb.append("}"); + sb.append("{"); + sb.append("id=").append((id)); + sb.append("} ").append((name)); + sb.append(" stockPrice=").append((stockPrice)); + sb.append(" inStock=").append((inStock)); + sb.append(" sellPrice=").append((sellPrice)); return sb.toString(); } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/RackCell.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/RackCell.java index 0a1de7f..4be5cc6 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/RackCell.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/RackCell.java @@ -2,25 +2,20 @@ * OpenAPI definition * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) * - * OpenAPI spec version: v0 - * + * OpenAPI spec version=v0 * - * NOTE: This class is auto generated by the swagger code generator program. + * + * NOTE=This class is auto generated by the swagger code generator program. * https://github.com/swagger-api/swagger-codegen.git * Do not edit the class manually. */ package ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model; -import java.util.Objects; -import java.util.Arrays; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import io.swagger.v3.oas.annotations.media.Schema; -import java.io.IOException; + +import java.util.Objects; /** * Продуктовая полка. На полке может находиться товар только одного вида, либо она может быть пустой. Полка может хранить определенное максимальное количество товаров. Также полки разнятся по заметности для покупателя (от 1 - самая незаметная, до 5 - максимально на виду */ @@ -180,15 +175,12 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("class RackCell {\n"); - - sb.append(" id: ").append(toIndentedString(id)).append("\n"); - sb.append(" visibility: ").append(toIndentedString(visibility)).append("\n"); - sb.append(" capacity: ").append(toIndentedString(capacity)).append("\n"); - sb.append(" productId: ").append(toIndentedString(productId)).append("\n"); - sb.append(" productName: ").append(toIndentedString(productName)).append("\n"); - sb.append(" productQuantity: ").append(toIndentedString(productQuantity)).append("\n"); - sb.append("}"); + sb.append("{").append((id)).append("}"); + sb.append(" visibility=").append((visibility)); + sb.append(" capacity=").append((capacity)); + sb.append(" productId=").append((productId)); + sb.append(" productName=").append((productName)); + sb.append(" productQuantity=").append((productQuantity)); return sb.toString(); } From 18109f1152f62845acde98c84f963a9dd5297078 Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Sun, 12 Sep 2021 23:32:16 +0400 Subject: [PATCH 08/12] commented Spring test --- .../psplayer/PerfectStorePlayerApplicationTests.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java b/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java index 89bb476..9cce6a5 100644 --- a/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java +++ b/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java @@ -1,12 +1,9 @@ package ru.hilariousstartups.javaskills.psplayer; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest +//@SpringBootTest class PerfectStorePlayerApplicationTests { - @Test + // @Test void contextLoads() { } From f08971becd94f7796424ea8a416225515849250c Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Mon, 13 Sep 2021 00:47:51 +0400 Subject: [PATCH 09/12] fixed bug: added sellPrice. removed some logs. added exception handling --- .../javaskills/psplayer/HRDepartment.java | 6 ++-- .../javaskills/psplayer/Merchandaizer.java | 34 +++++++++++++++++-- .../psplayer/PerfectStorePlayer.java | 25 +++++++++++--- .../swagger_codegen/model/Product.java | 9 +++-- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java index 92dd32e..e703d6f 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java @@ -41,9 +41,9 @@ public List firstHire() { } // TODO получать реальный id result.get(0).checkoutLineId(1); - log.info("Всего предложений:" + recruitmentAgency.size()); - log.info(recruitmentAgency.toString()); - log.info(result.toString()); + log.debug("Всего предложений:" + recruitmentAgency.size()); + log.debug(recruitmentAgency.toString()); + log.debug(result.toString()); return result; } } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java index a4121d3..51bb7ca 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java @@ -14,6 +14,28 @@ */ @Slf4j public class Merchandaizer { + + public enum Margin { + DOLLAR_ONE, PERCENT_10, PERCENT_20, PERCENT_50, PERCENT_100; + + public Margin next() { + switch (this) { + case DOLLAR_ONE: + return PERCENT_10; + case PERCENT_10: + return PERCENT_20; + case PERCENT_20: + return PERCENT_50; + case PERCENT_50: + return PERCENT_100; + case PERCENT_100: + return PERCENT_100; + default: + return DOLLAR_ONE; // never happens + } + } + } + private static final Integer DEFAULT_QUANTITY = 500; private List racks; private List stock; @@ -22,7 +44,6 @@ public class Merchandaizer { public Merchandaizer(List racks, List stock) { this.racks = new ArrayList<>(racks); this.stock = new ArrayList<>(stock); - log.info(stock.toString()); this.stock.sort(Comparator.comparing(Product::getStockPrice)); } @@ -67,9 +88,18 @@ private PutOnRackCellCommand putNextFromStock(RackCell rack) { productsOnRack.add(productToPutOnRack.getId()); final Integer inStock = productToPutOnRack.getInStock(); int rackCapacity = rack.getCapacity(); + double sellPrice = definePrice(productToPutOnRack); return new PutOnRackCellCommand().productId(productToPutOnRack.getId()) .rackCellId(rack.getId()) - .productQuantity(Math.min(inStock, rackCapacity)); + .productQuantity(Math.min(inStock, rackCapacity)) + .sellPrice(sellPrice); + } + + private double definePrice(Product product) { + if (product.getSellPrice() == null) { + return product.getStockPrice() + 1; + } + return product.getSellPrice() * 1.1; // add 10% } public List initialBuyIn() { diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index f1f0d04..0060b7d 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -41,17 +41,21 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { awaitServer(psApiClient); log.info("Подключение к серверу успешно. Начинаем игру"); + CurrentWorldResponse currentWorldResponse = null; try { - CurrentWorldResponse currentWorldResponse = null; int cnt = 0; do { cnt += 1; - if (cnt % 60 * 4 == 0) { + if (cnt % (60 * 4) == 0) { log.info("Пройден " + cnt + " тик"); // log.info("Ответ с предыдущего шага="+currentWorldResponse); } CurrentTickRequest request = createNextMove(currentWorldResponse); + if (cnt > 1 && cnt <= 3) { + log.info(request.getBuyStockCommands().toString()); + log.info(request.getPutOnRackCellCommands().toString()); + } if (currentWorldResponse == null) { currentWorldResponse = psApiClient.loadWorld(); @@ -65,7 +69,6 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { log.debug("Видов товаров=" + productAssortment + ", штук=" + productCountList + ", стоит=" + currentWorldResponse.getStockCosts()); log.debug("Полок=" + currentWorldResponse.getRackCells().size()); - log.info(currentWorldResponse.getRackCells().toString()); // end of logging // init state @@ -90,8 +93,10 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { while (!currentWorldResponse.isGameOver()); // Если пришел Game Over, значит все время игры закончилось. Пора считать прибыль - log.info("В корзинах:" + basketProducts.values()); log.info("Я заработал " + (currentWorldResponse.getIncome() - currentWorldResponse.getSalaryCosts() - currentWorldResponse.getStockCosts()) + "руб."); + log.info(currentWorldResponse.getStock().toString()); + log.info(currentWorldResponse.getRackCells().toString()); + log.info("В корзинах:" + basketProducts.values()); log.info("Sold products count=" + basketProducts.size()); var awaitingCheckoutCustomersCount = currentWorldResponse.getCustomers().stream(). filter(it -> it.getMode().equals(Customer.ModeEnum.WAIT_CHECKOUT)).count(); @@ -109,10 +114,22 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { } catch (ApiException e) { log.error(e.getMessage(), e); + closeStore(psApiClient, currentWorldResponse); } } + private void closeStore(PerfectStoreEndpointApi psApiClient, CurrentWorldResponse currentWorldResponse) { + CurrentTickRequest request = new CurrentTickRequest(); + do { + try { + currentWorldResponse = psApiClient.tick(request); + } catch (ApiException e) { + log.error("Failed to process empty request: " + e.getMessage()); + } + } while (!currentWorldResponse.isGameOver()); + } + private void collectDataFromAnswer(CurrentWorldResponse worldResponse) { var customers = worldResponse.getCustomers(); var awaitingCheckoutCustomers = customers.stream(). diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java index 591de9f..642fc6c 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java @@ -153,12 +153,11 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("{"); - sb.append("id=").append((id)); - sb.append("} ").append((name)); - sb.append(" stockPrice=").append((stockPrice)); + sb.append("{").append((id)).append("}"); + sb.append(" ").append((name)); + sb.append(" stock $").append((stockPrice)); sb.append(" inStock=").append((inStock)); - sb.append(" sellPrice=").append((sellPrice)); + sb.append(" sell $=").append((sellPrice)); return sb.toString(); } From f53c2ee1f18c43004a84e46ebf4c372b7a7d2310 Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Mon, 13 Sep 2021 01:30:40 +0400 Subject: [PATCH 10/12] fixed removal products from racks --- .../javaskills/psplayer/Merchandaizer.java | 63 ++++++++++++++----- .../psplayer/PerfectStorePlayer.java | 8 ++- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java index 51bb7ca..f625b7e 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java @@ -4,6 +4,7 @@ import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.*; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -36,10 +37,11 @@ public Margin next() { } } - private static final Integer DEFAULT_QUANTITY = 500; + private static final Integer DEFAULT_QUANTITY = 100; private List racks; private List stock; private List productsOnRack; + private List racksToPutOff; public Merchandaizer(List racks, List stock) { this.racks = new ArrayList<>(racks); @@ -47,11 +49,18 @@ public Merchandaizer(List racks, List stock) { this.stock.sort(Comparator.comparing(Product::getStockPrice)); } - public List inspectRacks(CurrentWorldResponse world) { + public List removeSold() { + var result = racksToPutOff.stream().map(id -> + new PutOffRackCellCommand().rackCellId(id) + ).collect(Collectors.toList()); + return result; + } + public List inspectRacks(CurrentWorldResponse world) { + racksToPutOff = new ArrayList<>(); racks = world.getRackCells(); stock = world.getStock(); - stock.sort(Comparator.comparing(Product::getStockPrice)); + stock.sort(Comparator.comparing(Product::getStockPrice).reversed()); productsOnRack = racks.stream() .filter(r -> r.getProductId() != null) .map(RackCell::getProductId) @@ -62,30 +71,42 @@ public List inspectRacks(CurrentWorldResponse world) { racks.stream() .filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)) .forEach(rack -> { - final PutOnRackCellCommand command = putNextFromStock(rack); + PutOnRackCellCommand command; + if (rack.getProductId() == null) { + command = putNextFromStock(rack); + } else { + command = addSame(rack); + } result.add(command); }); - return result; } - public List inspectStore() { - List result = new ArrayList<>(); - stock.stream().filter(it -> it.getInStock().equals(0)) - .forEach(product -> result.add(new BuyStockCommand() - .productId(product.getId()) - .quantity(DEFAULT_QUANTITY) - )); - return result; + private PutOnRackCellCommand addSame(RackCell rack) { + Product productToPutOnRack = stock.stream() + .filter(it -> it.getId().equals(rack.getProductId())) + .findAny() + .orElseThrow(() -> new IllegalStateException("Product is lost on stock:" + rack.getProductId())); + + final Integer inStock = productToPutOnRack.getInStock(); + int rackCapacity = rack.getCapacity(); + double sellPrice = definePrice(productToPutOnRack); + return new PutOnRackCellCommand().productId(productToPutOnRack.getId()) + .rackCellId(rack.getId()) + .productQuantity(Math.min(inStock, rackCapacity)) + .sellPrice(sellPrice); } + private PutOnRackCellCommand putNextFromStock(RackCell rack) { Product productToPutOnRack; productToPutOnRack = stock.stream() .filter(product -> !productsOnRack.contains(product.getId()) && product.getInStock() > 0) - .findFirst().orElse(null); + .findFirst() + .orElseThrow(() -> new IllegalStateException("Store is empty")); productsOnRack.add(productToPutOnRack.getId()); + racksToPutOff.add(rack.getId()); final Integer inStock = productToPutOnRack.getInStock(); int rackCapacity = rack.getCapacity(); double sellPrice = definePrice(productToPutOnRack); @@ -102,6 +123,20 @@ private double definePrice(Product product) { return product.getSellPrice() * 1.1; // add 10% } + public List inspectStore() { + List result = new ArrayList<>(); + stock.stream().filter(it -> it.getInStock().equals(0)) + .forEach(product -> result.add(new BuyStockCommand() + .productId(product.getId()) + .quantity(DEFAULT_QUANTITY) + )); + if (result.size() > 5) { + return result; + } else { + return Collections.emptyList();// избегаем мелких закупок + } + } + public List initialBuyIn() { List buyStockCommands = new ArrayList<>(50); stock.forEach(it -> buyStockCommands.add( diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index 0060b7d..6cc0207 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -86,7 +86,7 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { request.buyStockCommands(merch.initialBuyIn()); } - currentWorldResponse = psApiClient.tick(request); + currentWorldResponse = psApiClient.tick(new CurrentTickRequest()); collectDataFromAnswer(currentWorldResponse); } @@ -112,7 +112,7 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { currentWorldResponse.getCustomers().size() )); - } catch (ApiException e) { + } catch (ApiException | RuntimeException e) { log.error(e.getMessage(), e); closeStore(psApiClient, currentWorldResponse); } @@ -168,8 +168,10 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { // готовимся закупать товар на склад и выставлять его на полки final List putOnRackCellCommands = merch.inspectRacks(worldResponse); - request.setPutOnRackCellCommands(putOnRackCellCommands); final List buyStockCommands = merch.inspectStore(); + final List putOffRackCellCommands = merch.removeSold(); + request.setPutOffRackCellCommands(putOffRackCellCommands); + request.setPutOnRackCellCommands(putOnRackCellCommands); request.buyStockCommands(buyStockCommands); /*ArrayList buyStockCommands = new ArrayList<>(); request.setBuyStockCommands(buyStockCommands); From 42f9524ae747ac58d9209135837cd352c3349bd2 Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Thu, 16 Dec 2021 21:53:50 +0400 Subject: [PATCH 11/12] removed commented code --- .../psplayer/PerfectStorePlayer.java | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index 6cc0207..697c921 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -173,62 +173,7 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { request.setPutOffRackCellCommands(putOffRackCellCommands); request.setPutOnRackCellCommands(putOnRackCellCommands); request.buyStockCommands(buyStockCommands); - /*ArrayList buyStockCommands = new ArrayList<>(); - request.setBuyStockCommands(buyStockCommands); - - ArrayList putOnRackCellCommands = new ArrayList<>(); - - - List stock = worldResponse.getStock(); - List rackCells = worldResponse.getRackCells(); - - // Обходим торговый зал и смотрим какие полки пустые. Выставляем на них товар. - /*worldResponse.getRackCells().stream() - .filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)) - .forEach(rack -> { - Product productToPutOnRack = null; - if (rack.getProductId() == null) { - List productsOnRack = rackCells.stream().filter(r -> r.getProductId() != null).map(RackCell::getProductId).collect(Collectors.toList()); - productsOnRack.addAll(putOnRackCellCommands.stream().map(c -> c.getProductId()).collect(Collectors.toList())); - productToPutOnRack = stock.stream() - .filter(product -> !productsOnRack.contains(product.getId())) - .findFirst().orElse(null); - } else { - productToPutOnRack = stock.stream() - .filter(product -> product.getId().equals(rack.getProductId())) - .findFirst().orElse(null); - } - if (productToPutOnRack == null) { - productToPutOnRack = stock.stream().filter(product -> product.getInStock() > 0) - .findFirst().orElse(null); - } - - Integer productQuantity = rack.getProductQuantity(); - if (productQuantity == null) { - productQuantity = 0; - } - - // Вначале закупим товар на склад. Каждый ход закупать товар накладно, но ведь это тестовый игрок. - Integer orderQuantity = rack.getCapacity() - productQuantity; - if (productToPutOnRack.getInStock() < orderQuantity) { - BuyStockCommand command = new BuyStockCommand(); - command.setProductId(productToPutOnRack.getId()); - command.setQuantity(100); - buyStockCommands.add(command); - } - - // Далее разложим на полки. И сформируем цену. - PutOnRackCellCommand command = new PutOnRackCellCommand(); - command.setProductId(productToPutOnRack.getId()); - command.setRackCellId(rack.getId()); - command.setProductQuantity(orderQuantity); - if (productToPutOnRack.getSellPrice() == null) { - final int margin = (int)Math.round(productToPutOnRack.getStockPrice()*0.1); - command.setSellPrice(productToPutOnRack.getStockPrice() + margin); - } - putOnRackCellCommands.add(command); - });*/ return request; } From 990f54a2db4eb5f3f0267d48df8fc618598c8365 Mon Sep 17 00:00:00 2001 From: Argeriskul Date: Thu, 16 Dec 2021 22:21:29 +0400 Subject: [PATCH 12/12] temp commit --- .../javaskills/psplayer/PerfectStorePlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index 697c921..6303e7f 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -173,7 +173,7 @@ private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { request.setPutOffRackCellCommands(putOffRackCellCommands); request.setPutOnRackCellCommands(putOnRackCellCommands); request.buyStockCommands(buyStockCommands); - + System.out.println("balbla"); return request; }