/ assign6 / GridCore.java
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  }