aboutsummaryrefslogtreecommitdiffstats
path: root/ui-console/src/main/java/market/guess/ui/console
diff options
context:
space:
mode:
authorKostya <mail@sartin.in>2026-08-18 18:19:50 +0300
committerKostya <mail@sartin.in>2026-08-18 18:19:50 +0300
commit755d2265054e5140e82d322681566c3034583c95 (patch)
tree0220f9d04affa03fabf3c2bc68c51c01ee1475d9 /ui-console/src/main/java/market/guess/ui/console
parent9dab2b3d624ecae08439532157fb8b9805fe9274 (diff)
downloadguess-market-755d2265054e5140e82d322681566c3034583c95.tar.gz
guess-market-755d2265054e5140e82d322681566c3034583c95.tar.xz
guess-market-755d2265054e5140e82d322681566c3034583c95.zip
Break down purchase receipts by cost/commission, harden XML validation and state I/O
Adds a full pre-load validation pass for GM XML seeds (ids, names, options, commission, LMSR liquidity), replaces IO.print with System.out, and surfaces detailed error messages across load/save commands instead of generic failures.
Diffstat (limited to 'ui-console/src/main/java/market/guess/ui/console')
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java31
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java19
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java10
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java10
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java17
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java16
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java20
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java31
8 files changed, 107 insertions, 47 deletions
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java
index 2ad7a53..bdd3eb1 100644
--- a/ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java
+++ b/ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java
@@ -29,7 +29,7 @@ public final class EventDetailsMenuCommand implements MenuCommand {
29 var result = catalog.getAllEvents(); 29 var result = catalog.getAllEvents();
30 30
31 if (!result.isSuccess()) { 31 if (!result.isSuccess()) {
32 io.println("Failed to list events"); 32 io.println("Failed to list events: %s.".formatted(result.getMessage()));
33 return; 33 return;
34 } 34 }
35 35
@@ -37,11 +37,17 @@ public final class EventDetailsMenuCommand implements MenuCommand {
37 var viewable = events.stream().filter(event -> event.status() != EventStatus.DRAFT).toList(); 37 var viewable = events.stream().filter(event -> event.status() != EventStatus.DRAFT).toList();
38 38
39 if (viewable.isEmpty()) { 39 if (viewable.isEmpty()) {
40 io.println("There are currently no events to show."); 40 io.println("No events are currently loaded. Please load a valid XML file first.");
41 return; 41 return;
42 } 42 }
43 43
44 var pick = io.readSelect("Pick an event: ", viewable, event -> "%s".formatted(event.name())); 44 io.newLine();
45 io.println("Available events:");
46 var pick =
47 io.readSelect(
48 "Pick an event: ",
49 viewable,
50 event -> "[ID: %d] %s (%s)".formatted(event.displayId(), event.name(), event.status()));
45 51
46 var result2 = catalog.getEvent(pick.key()); 52 var result2 = catalog.getEvent(pick.key());
47 var event = result2.getData(); 53 var event = result2.getData();
@@ -50,11 +56,26 @@ public final class EventDetailsMenuCommand implements MenuCommand {
50 return; 56 return;
51 } 57 }
52 58
59 io.newLine();
60 io.println("=== Event Details ===");
61 io.println("Event ID: %d".formatted(event.summary().displayId()));
62 io.println("Name: %s".formatted(event.summary().name()));
63 io.println("Description: %s".formatted(event.summary().description()));
64 io.println(
65 "Commission: %d%% (%s)"
66 .formatted(
67 event.summary().commissionPercent(),
68 event.summary().commissionTiming() == market.guess.api.CommissionTiming.ON_CLOSE
69 ? "on-close"
70 : "on-purchase"));
71 io.println("Status: %s".formatted(event.summary().status()));
72
53 io.printState(event.state()); 73 io.printState(event.state());
54 io.printHistory(event.history()); 74 io.printHistory(event.history());
55 75
56 if (!event.settledMarket().isBlank()) { 76 if (event.settledMarket() != null && !event.settledMarket().isBlank()) {
57 io.println("Winning market: %s".formatted(event.settledMarket())); 77 io.newLine();
78 io.println("Winning option: %s".formatted(event.settledMarket()));
58 } 79 }
59 } 80 }
60} 81}
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java
index 755bf37..01e93bd 100644
--- a/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java
+++ b/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java
@@ -29,24 +29,29 @@ public final class ListEventsMenuCommand implements MenuCommand {
29 var result = catalog.getAllEvents(); 29 var result = catalog.getAllEvents();
30 30
31 if (!result.isSuccess()) { 31 if (!result.isSuccess()) {
32 io.println("Failed to list events due to %s.".formatted(result.getMessage())); 32 io.println("Failed to list events: %s.".formatted(result.getMessage()));
33 return; 33 return;
34 } 34 }
35 var events = result.getData(); 35 var events = result.getData();
36 36
37 if (events.isEmpty()) {
38 io.println("No events are currently loaded. Please load a valid XML file first.");
39 return;
40 }
41
37 for (var event : events) { 42 for (var event : events) {
38 io.newLine(); 43 io.newLine();
39 io.println(" %s".formatted(event.name())); 44 io.println(" [%d] %s (ID: %d)".formatted(event.displayId(), event.name(), event.displayId()));
40 io.println(" %s".formatted(event.description())); 45 io.println(" Description: %s".formatted(event.description()));
41 io.println( 46 io.println(
42 " Commission: %d%% (%s)" 47 " Commission: %d%% (%s)"
43 .formatted(event.commissionPercent(), timing(event.commissionTiming()))); 48 .formatted(event.commissionPercent(), timing(event.commissionTiming())));
44 io.println(" Options: " + String.join(" | ", event.optionNames())); 49 io.println(" Options: " + String.join(" | ", event.optionNames()));
45 io.println(" Status: " + event.status()); 50 io.println(" Status: " + event.status());
46 } 51 }
47 } 52 }
48 53
49 private static String timing(CommissionTiming timing) { 54 private static String timing(CommissionTiming timing) {
50 return timing == CommissionTiming.ON_CLOSE ? "charged on close" : "charged on purchase"; 55 return timing == CommissionTiming.ON_CLOSE ? "on-close" : "on-purchase";
51 } 56 }
52} 57}
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java
index c5b3c77..c36df90 100644
--- a/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java
+++ b/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java
@@ -24,16 +24,20 @@ public final class LoadFileMenuCommand implements MenuCommand {
24 24
25 @Override 25 @Override
26 public void execute() { 26 public void execute() {
27 var path = io.readPath("Pleae enter the full file path to the XML file: "); 27 var path = io.readPath("Please enter the full file path to the XML file: ");
28 var result = catalog.loadEvents(path); 28 var result = catalog.loadEvents(path);
29 29
30 if (result.isSuccess()) { 30 if (result.isSuccess()) {
31 io.println( 31 io.println(
32 "Loaded %d event(s) from %s." 32 "Successfully loaded %d event(s) from %s."
33 .formatted(result.getData().eventsLoaded(), result.getData().source())); 33 .formatted(result.getData().eventsLoaded(), result.getData().source()));
34 return; 34 return;
35 } 35 }
36 36
37 io.println("The file was not loaded, %s ".formatted(result.getDetails())); 37 var errorMsg =
38 result.getDetails() != null && !result.getDetails().isBlank()
39 ? result.getDetails()
40 : result.getMessage();
41 io.println("The file was not loaded: %s".formatted(errorMsg));
38 } 42 }
39} 43}
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java
index 30acef3..8fa77c2 100644
--- a/ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java
+++ b/ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java
@@ -25,11 +25,17 @@ public final class LoadMarketStateMenuCommand implements MenuCommand {
25 25
26 @Override 26 @Override
27 public void execute() { 27 public void execute() {
28 var path = io.readPath("Which state file to load? "); 28 var path = io.readPath("Please enter full path to state file (without or with .json extension): ");
29 var result = catalog.restoreState(path); 29 var result = catalog.restoreState(path);
30 30
31 if (result.isSuccess()) { 31 if (result.isSuccess()) {
32 io.println("Restored %d event(s) from %s.".formatted(result.getData().eventsLoaded(), path)); 32 io.println("Successfully restored %d event(s) from %s.".formatted(result.getData().eventsLoaded(), path));
33 } else {
34 var msg =
35 result.getDetails() != null && !result.getDetails().isBlank()
36 ? result.getDetails()
37 : result.getMessage();
38 io.println("Failed to restore state: %s".formatted(msg));
33 } 39 }
34 } 40 }
35} 41}
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java
index a03f757..0d58b1d 100644
--- a/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java
+++ b/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java
@@ -34,11 +34,16 @@ public final class PlaceOrderMenuCommand implements MenuCommand {
34 var result = catalog.getAllEvents(); 34 var result = catalog.getAllEvents();
35 35
36 if (!result.isSuccess()) { 36 if (!result.isSuccess()) {
37 io.println("Failed to list events"); 37 io.println("Failed to list events: %s.".formatted(result.getMessage()));
38 return; 38 return;
39 } 39 }
40 40
41 var events = result.getData(); 41 var events = result.getData();
42 if (events.isEmpty()) {
43 io.println("No events are currently loaded. Please load a valid XML file first.");
44 return;
45 }
46
42 var active = events.stream().filter(event -> event.status() == EventStatus.ACTIVE).toList(); 47 var active = events.stream().filter(event -> event.status() == EventStatus.ACTIVE).toList();
43 48
44 if (active.isEmpty()) { 49 if (active.isEmpty()) {
@@ -46,7 +51,9 @@ public final class PlaceOrderMenuCommand implements MenuCommand {
46 return; 51 return;
47 } 52 }
48 53
49 var pick = io.readSelect("Pick an event: ", active, event -> "%s".formatted(event.name())); 54 io.newLine();
55 io.println("Active events:");
56 var pick = io.readSelect("Pick an active event: ", active, event -> "[ID: %d] %s".formatted(event.displayId(), event.name()));
50 57
51 var result2 = catalog.getEvent(pick.key()); 58 var result2 = catalog.getEvent(pick.key());
52 var event = result2.getData(); 59 var event = result2.getData();
@@ -59,11 +66,11 @@ public final class PlaceOrderMenuCommand implements MenuCommand {
59 66
60 var market = 67 var market =
61 io.readSelect( 68 io.readSelect(
62 "Choose a market: ", 69 "Choose an option to buy: ",
63 event.state().markets(), 70 event.state().markets(),
64 o -> "%s [value %s, %s shares helds]".formatted(o.name(), o.price(), o.volume())); 71 o -> "%s [current value: %s, %s shares purchased]".formatted(o.name(), o.price(), o.volume()));
65 72
66 var amount = io.readInt("How many shares? ", 1, Integer.MAX_VALUE); 73 var amount = io.readInt("How many shares would you like to buy? ", 1, Integer.MAX_VALUE);
67 74
68 var buyResult = 75 var buyResult =
69 context.placeOrder( 76 context.placeOrder(
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java
index 3c5d6b9..78a5ca0 100644
--- a/ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java
+++ b/ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java
@@ -25,13 +25,17 @@ public final class SaveMarketStateMenuCommand implements MenuCommand {
25 25
26 @Override 26 @Override
27 public void execute() { 27 public void execute() {
28 var path = io.readPath("Save to: "); 28 var path = io.readPath("Please enter full path to save file (without or with .json extension): ");
29 29
30 try { 30 var result = catalog.saveState(path);
31 catalog.saveState(path); 31 if (result.isSuccess()) {
32 io.println("Saved current state to %s.".formatted(path)); 32 io.println("Successfully saved current market state to %s.".formatted(path));
33 } catch (Exception e) { 33 } else {
34 io.println("Failed to save: %s".formatted(e.getMessage())); 34 var msg =
35 result.getDetails() != null && !result.getDetails().isBlank()
36 ? result.getDetails()
37 : result.getMessage();
38 io.println("Failed to save state: %s".formatted(msg));
35 } 39 }
36 } 40 }
37} 41}
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java
index 3457e08..bec25a7 100644
--- a/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java
+++ b/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java
@@ -32,11 +32,16 @@ public final class SettleEventMenuCommand implements MenuCommand {
32 var result = catalog.getAllEvents(); 32 var result = catalog.getAllEvents();
33 33
34 if (!result.isSuccess()) { 34 if (!result.isSuccess()) {
35 io.println("Failed to list events"); 35 io.println("Failed to list events: %s.".formatted(result.getMessage()));
36 return; 36 return;
37 } 37 }
38 38
39 var events = result.getData(); 39 var events = result.getData();
40 if (events.isEmpty()) {
41 io.println("No events are currently loaded. Please load a valid XML file first.");
42 return;
43 }
44
40 var active = events.stream().filter(event -> event.status() == EventStatus.ACTIVE).toList(); 45 var active = events.stream().filter(event -> event.status() == EventStatus.ACTIVE).toList();
41 46
42 if (active.isEmpty()) { 47 if (active.isEmpty()) {
@@ -44,7 +49,9 @@ public final class SettleEventMenuCommand implements MenuCommand {
44 return; 49 return;
45 } 50 }
46 51
47 var pick = io.readSelect("Pick an event: ", active, event -> "%s".formatted(event.name())); 52 io.newLine();
53 io.println("Active events to settle:");
54 var pick = io.readSelect("Pick an event to settle: ", active, event -> "[ID: %d] %s".formatted(event.displayId(), event.name()));
48 55
49 var result2 = catalog.getEvent(pick.key()); 56 var result2 = catalog.getEvent(pick.key());
50 var event = result2.getData(); 57 var event = result2.getData();
@@ -56,12 +63,17 @@ public final class SettleEventMenuCommand implements MenuCommand {
56 io.printState(event.state()); 63 io.printState(event.state());
57 64
58 var winner = 65 var winner =
59 io.readSelect("Which market won? ", event.state().markets(), o -> "%s".formatted(o.name())); 66 io.readSelect("Which option won? ", event.state().markets(), o -> "%s".formatted(o.name()));
60 67
61 var settleResult = context.settleEvent("Tester", event.summary().key(), winner.key()); 68 var settleResult = context.settleEvent("Tester", event.summary().key(), winner.key());
62 if (settleResult.isSuccess()) { 69 if (settleResult.isSuccess()) {
70 var settledEvent = settleResult.getData();
71 io.newLine();
72 io.println("Event closed successfully! Winning option: %s".formatted(settledEvent.settledMarket()));
73 io.printState(settledEvent.state());
74 io.printHistory(settledEvent.history());
63 io.newLine(); 75 io.newLine();
64 io.println("Settled. Winning market: %s".formatted(settleResult.getData().settledMarket())); 76 io.println("Winning option: %s".formatted(settledEvent.settledMarket()));
65 return; 77 return;
66 } 78 }
67 io.newLine(); 79 io.newLine();
diff --git a/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java b/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java
index 721cf8b..3ff752c 100644
--- a/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java
+++ b/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java
@@ -20,20 +20,20 @@ public final class InputProcessor {
20 private final Scanner scanner = new Scanner(System.in); 20 private final Scanner scanner = new Scanner(System.in);
21 21
22 public void print(Object object) { 22 public void print(Object object) {
23 IO.print(object); 23 System.out.print(object);
24 } 24 }
25 25
26 public void println(Object object) { 26 public void println(Object object) {
27 IO.println(object); 27 System.out.println(object);
28 } 28 }
29 29
30 public void newLine() { 30 public void newLine() {
31 IO.println(""); 31 System.out.println("");
32 } 32 }
33 33
34 public void splashScreen() { 34 public void splashScreen() {
35 newLine(); 35 newLine();
36 IO.println(padLeft("<<- GUESS MARKET ->>", 36)); 36 System.out.println(padLeft("<<- GUESS MARKET ->>", 36));
37 } 37 }
38 38
39 public void printState(MarketStateDTO state) { 39 public void printState(MarketStateDTO state) {
@@ -68,7 +68,8 @@ public final class InputProcessor {
68 68
69 public String readLine(String prompt) { 69 public String readLine(String prompt) {
70 print(prompt); 70 print(prompt);
71 return nextLine().trim(); 71 var line = nextLine();
72 return line != null ? line.trim() : "";
72 } 73 }
73 74
74 public int readInt(String prompt, int min, int max) { 75 public int readInt(String prompt, int min, int max) {
@@ -80,7 +81,6 @@ public final class InputProcessor {
80 } 81 }
81 try { 82 try {
82 var value = Integer.parseInt(raw); 83 var value = Integer.parseInt(raw);
83
84 if (value > max || value < min) { 84 if (value > max || value < min) {
85 println( 85 println(
86 "%d is out of range, please enter a number between %d and %d." 86 "%d is out of range, please enter a number between %d and %d."
@@ -90,9 +90,8 @@ public final class InputProcessor {
90 return value; 90 return value;
91 } catch (NumberFormatException e) { 91 } catch (NumberFormatException e) {
92 println( 92 println(
93 "%s in not a number, please enter a number between %d and %d." 93 "'%s' is not a valid number, please enter a number between %d and %d."
94 .formatted(raw, min, max)); 94 .formatted(raw, min, max));
95 continue;
96 } 95 }
97 } 96 }
98 } 97 }
@@ -100,12 +99,10 @@ public final class InputProcessor {
100 public Path readPath(String prompt) { 99 public Path readPath(String prompt) {
101 while (true) { 100 while (true) {
102 var raw = readLine(prompt); 101 var raw = readLine(prompt);
103
104 if (raw.isBlank()) { 102 if (raw.isBlank()) {
105 println("Please enter a file path."); 103 println("Please enter a file path.");
106 continue; 104 continue;
107 } 105 }
108
109 try { 106 try {
110 return Path.of(unquote(raw)); 107 return Path.of(unquote(raw));
111 } catch (InvalidPathException e) { 108 } catch (InvalidPathException e) {
@@ -126,7 +123,6 @@ public final class InputProcessor {
126 public boolean readYesNo(String prompt) { 123 public boolean readYesNo(String prompt) {
127 while (true) { 124 while (true) {
128 var raw = readLine("%s [Y/N]: ".formatted(prompt)).toLowerCase(); 125 var raw = readLine("%s [Y/N]: ".formatted(prompt)).toLowerCase();
129
130 switch (raw) { 126 switch (raw) {
131 case "y", "yes" -> { 127 case "y", "yes" -> {
132 return true; 128 return true;
@@ -134,14 +130,14 @@ public final class InputProcessor {
134 case "n", "no" -> { 130 case "n", "no" -> {
135 return false; 131 return false;
136 } 132 }
137 default -> IO.println("Please answer y/yes or n/no."); 133 default -> println("Please answer y/yes or n/no.");
138 } 134 }
139 } 135 }
140 } 136 }
141 137
142 public <T> T readSelect(String prompt, List<T> items, Function<T, String> function) { 138 public <T> T readSelect(String prompt, List<T> items, Function<T, String> function) {
143 for (var i = 0; i < items.size(); i++) { 139 for (var i = 0; i < items.size(); i++) {
144 IO.println(" [%d]->> %s".formatted(i + 1, function.apply(items.get(i)))); 140 println(" [%d]->> %s".formatted(i + 1, function.apply(items.get(i))));
145 } 141 }
146 142
147 var pick = readInt(prompt, 1, items.size()); 143 var pick = readInt(prompt, 1, items.size());
@@ -156,12 +152,16 @@ public final class InputProcessor {
156 public void printReceipt(PurchaseReceiptDTO receipt) { 152 public void printReceipt(PurchaseReceiptDTO receipt) {
157 newLine(); 153 newLine();
158 println("Purchase confirmed:"); 154 println("Purchase confirmed:");
159 println(tradeRow("Option", "Qty", "Paid", "At")); 155 println(tradeRow("Option", "Qty", "Cost", "At"));
160 println("-".repeat(OPTION_WIDTH + QTY_WIDTH + PAID_WIDTH + AT_WIDTH + 3)); 156 println("-".repeat(OPTION_WIDTH + QTY_WIDTH + PAID_WIDTH + AT_WIDTH + 3));
161 157
162 for (var trade : receipt.trades()) { 158 for (var trade : receipt.trades()) {
163 println(tradeRow(trade.optionName(), trade.quantity(), trade.pricePaid(), trade.at())); 159 println(tradeRow(trade.optionName(), trade.quantity(), trade.pricePaid(), trade.at()));
164 } 160 }
161 newLine();
162 println("Shares cost: %s".formatted(receipt.sharesCost()));
163 println("Commission paid: %s".formatted(receipt.commissionPaid()));
164 println("Total amount paid: %s".formatted(receipt.totalPaid()));
165 165
166 printState(receipt.stateAfter()); 166 printState(receipt.stateAfter());
167 } 167 }
@@ -175,7 +175,7 @@ public final class InputProcessor {
175 return; 175 return;
176 } 176 }
177 177
178 println(tradeRow("Option", "Qty", "Paid", "At")); 178 println(tradeRow("Option", "Qty", "Cost", "At"));
179 println("-".repeat(OPTION_WIDTH + QTY_WIDTH + PAID_WIDTH + AT_WIDTH + 3)); 179 println("-".repeat(OPTION_WIDTH + QTY_WIDTH + PAID_WIDTH + AT_WIDTH + 3));
180 180
181 for (var trade : history) { 181 for (var trade : history) {
@@ -192,3 +192,4 @@ public final class InputProcessor {
192 padRight(at, AT_WIDTH)); 192 padRight(at, AT_WIDTH));
193 } 193 }
194} 194}
195