GridCore.java
1 import java.util.Random; 2 3 /// Grid measures - core executable and embeddable class 4 /// 5 /// SPDX-License-Identifier: GPL-3.0-or-later 6 /// SPDX-FileCopyrightText: 2024 Jonas Smedegaard <dr@jones.dk> 7 /// 8 /// Compute distance from center within a 9x9 grid 9 /// of the centermost vertically or horisontally aligned objects 10 /// among 10 randomly placed objects. 11 /// 12 /// Core class usable as self-contained executable via method main() 13 /// and also embedded in a larger system by instantiating GridCore(). 14 /// 15 /// * v0.0.2 16 /// * fix distance calculation 17 /// * v0.0.1 18 /// * initial release, as delivery "assignment 6" 19 /// 20 /// @version 0.0.2 21 /// @see <https://app.radicle.xyz/nodes/ash.radicle.garden/rad:z3YzABgyz2D36LiKe3YcdJ6PcDCXM/tree/assign6/README.md> 22 /// @see <https://moodle.ruc.dk/mod/assign/view.php?id=508549> 23 public class GridCore { 24 25 /// Grid constructor 26 /// 27 /// @param coordinates location of observer 28 /// @param thingcount amount of things to populate 29 public GridCore(final int[] coordinates, final int thingcount) { 30 Place place = new Place(coordinates); 31 obs = new Agent(place, 'X'); 32 add('X', place); 33 for (int i = 0; i < thingcount; i++) { 34 add('o'); 35 } 36 } 37 38 /// JVM entry point 39 /// 40 /// @param args command-line arguments (ignored) 41 public static void main(final String[] args) { 42 43 // Instantiation as dictated by assignment 44 final int population = 10; 45 final int[] observationpoint = new int[] {5, 5}; 46 GridCore grid = new GridCore(observationpoint, population); 47 48 // minimal viable product 49 System.out.println(grid.map() 50 .replace("\\n", System.lineSeparator())); 51 for (Bearing bearing: Bearing.values()) { 52 System.out.printf("Distance %s = %s%n", 53 bearing, grid.view(bearing)); 54 } 55 } 56 57 /// instantiate a random number generator 58 private static Random rand = new Random(); 59 60 /// Lowest position in grid 61 public static final Place MIN = new Place(new int[] {1, 1}); 62 63 /// highest position in grid 64 public static final Place MAX = new Place(new int[] {9, 9}); 65 66 /// size of grid 67 public static final int[] LEN = {9, 9}; 68 69 /// Physical position in grid 70 /// 71 /// Defined using record type to tersely define as immutable object 72 /// 73 /// @param coords coordinates as integer array of each Dimension 74 private record Place(int[] coords) { 75 76 /// Input validation 77 /// 78 /// Ensure uniform array size compatible with grid. 79 /// 80 /// This simplifies (and optimizes) internal calculations 81 /// by not needing array length check at each computation 82 /// 83 /// @throws IllegalArgumentException must fit Dimension set 84 private Place { 85 if (coords.length != 2) { 86 throw new IllegalArgumentException( 87 "grid system mismatch" 88 ); 89 } 90 } 91 92 /// Get coordinates relative to another Place 93 /// 94 /// @param baseline reference Place 95 /// @return coordinates as integer array 96 public int[] relativeTo(final Place baseline) { 97 int[] newplace = new int[coords.length]; 98 for (int i = 0; i < coords.length; i++) { 99 newplace[i] = coords[i] - baseline.coords[i]; 100 } 101 return newplace; 102 } 103 } 104 105 /// Physical presence 106 /// 107 /// Defined as an interface rather than an abstract class 108 /// to allow polymorphism also for final classes like a record. 109 /// 110 /// @see <https://stackoverflow.com/a/15931312/18619283> 111 /// @see <https://stackoverflow.com/a/63616742/18619283> 112 private interface Presence { 113 114 /// Representation 115 /// 116 /// @return visual marker in form of a single character 117 char mark(); 118 } 119 120 /// Passive static thing 121 /// 122 /// Defined using record type to tersely define as immutable object 123 /// 124 /// @param place position in grid 125 /// @param mark visual representation 126 private record Object(Place place, char mark) implements Presence { } 127 128 /// Observer 129 private Agent obs; 130 131 /// Viewing orientations in grid 132 /// 133 /// Defined using enum type to compactly store finite options 134 /// and referencing using semantic terms 135 /// while leaving open the possibility of expanding options 136 /// 137 /// Each enum term contains static functional integer values 138 /// accessed via embedded methods 139 public enum Bearing { 140 141 /// Northbound 142 NORTH(0, 1), 143 144 /// Eastbound 145 EAST(1, 0), 146 147 /// Southbound 148 SOUTH(0, -1), 149 150 /// Westbound 151 WEST(-1, 0); 152 153 /// Bearing constructor 154 /// 155 /// @param i abscissa component of unit vector (x-value) 156 /// @param j ordinate component of unit vector (y-value) 157 Bearing(final int i, final int j) { 158 this.vector = new int[] {i, j}; 159 this.axis = (i != 0) ? 0 : 1; 160 } 161 162 /// Unit vector 163 /// 164 /// The unit vector as an integer array. 165 public final int[] vector; 166 167 /// Zero-based dimension alignment of bearing 168 /// 169 /// Axis that the bearing is oriented along, as integer value 170 public final int axis; 171 }; 172 173 /// Active thing 174 private class Agent implements Presence { 175 176 /// Constructor 177 /// 178 /// @param place position in grid 179 /// @param mark visual representation 180 Agent(final Place place, final char mark) { 181 this.place = place; 182 this.mark = mark; 183 } 184 185 /// Position in grid 186 private Place place; 187 188 /// Representation 189 private char mark; 190 191 /// Get place 192 /// 193 /// @return position in grid 194 public Place place() { 195 return place; 196 } 197 198 /// Get representation 199 /// 200 /// @return visual marker in form of a single character 201 @Override 202 public char mark() { 203 return mark; 204 } 205 } 206 207 /// collection of uniquely occupied places in the grid 208 private Object[][] places = new Object[LEN[0]][LEN[1]]; 209 210 /// Place a thing at some place in the grid 211 /// 212 /// @param thing the thing to place in form of a character 213 /// @param place the place where the thing gets placed 214 private void add(final char thing, final Place place) { 215 // TODO: log warning if replacing another thing 216 // if (places[x][y] != null) { 217 // } 218 int[] rel = place.relativeTo(MIN); 219 this.places[rel[0]][rel[1]] = new Object(place, thing); 220 } 221 222 // TODO: count attempts and bail if exceeding possible slots 223 // TODO: randomly pick among actually vacant positions instead 224 /// Place a thing at a random empty place in the grid 225 /// 226 /// @param thing the thing to place in form of a character 227 public final void add(final char thing) { 228 int x; 229 int y; 230 while (true) { 231 x = rand.nextInt(LEN[0] - 1) + MIN.coords[0]; 232 y = rand.nextInt(LEN[1] - 1) + MIN.coords[1]; 233 if (places[x][y] == null) { 234 places[x][y] = new Object(new Place(new int[] {x, y}), thing); 235 break; 236 } 237 } 238 } 239 240 /// Compose a POSIX ASCII map of grid contents 241 /// 242 /// @return ASCII map as a rectangular multi-line string 243 public final String map() { 244 StringBuilder sb = new StringBuilder(); 245 246 for (int i = 0; i < LEN[0]; i++) { 247 for (int j = 0; j < LEN[1]; j++) { 248 if (places[i][j] == null) { 249 sb.append('.'); 250 } else { 251 sb.append(places[i][j].mark()); 252 } 253 254 // space-delimit horisontally 255 if (j < places[i].length - 1) { 256 sb.append(' '); 257 } 258 } 259 260 // POSIX-newline-delimit vertically 261 if (i < places.length - 1) { 262 sb.append('\n'); 263 } 264 } 265 return sb.toString(); 266 } 267 268 /// Compute line of sight 269 /// 270 /// @param bearing viewing orientation 271 /// @return visibility distance 272 public final int view(final Bearing bearing) { 273 int[] pos = obs.place().relativeTo(MIN); 274 int dir = bearing.vector[bearing.axis]; 275 int[] range = (dir > 0) 276 ? MAX.relativeTo(obs.place()) 277 : obs.place().relativeTo(MIN); 278 int i = 1; 279 280 // TODO: avoid hardcoded height dimension mirroring 281 final int flip = -1; 282 283 // count in each case from observation point towards an object 284 // TODO: make more DRY 285 switch (bearing) { 286 case NORTH: 287 for (; i <= range[1]; i++) { 288 if (places[pos[1] + i * dir * flip][pos[0]] != null) { 289 return i; 290 } 291 } 292 break; 293 case EAST: 294 for (; i <= range[0]; i++) { 295 if (places[pos[1]][pos[0] + i * dir] != null) { 296 return i; 297 } 298 } 299 break; 300 case SOUTH: 301 for (; i <= range[1]; i++) { 302 if (places[pos[1] + i * dir * flip][pos[0]] != null) { 303 return i; 304 } 305 } 306 break; 307 case WEST: 308 for (; i <= range[0]; i++) { 309 if (places[pos[1]][pos[0] + i * dir] != null) { 310 return i; 311 } 312 } 313 break; 314 315 // no-op: javac is unaware that we can never reach here 316 default: 317 return -1; 318 } 319 320 // return distance to horizon (or "ice wall") 321 // @see <https://wiki.tfes.org/The_Ice_Wall> 322 return i; 323 } 324 }