/ Roster.java
Roster.java
1 //FIXME import java.util.HashMap; 2 import java.util.Random; 3 4 /// Cave - Roster of entities in a cave 5 /// 6 /// SPDX-License-Identifier: GPL-3.0-or-later 7 /// SPDX-FileCopyrightText: 2024 Jonas Smedegaard <dr@jones.dk> 8 /// 9 /// Knowledge about placement of entities in a cave. 10 /// 11 /// * v0.0.1 12 /// * initial release, derived frop delivery "assignment 6" 13 /// 14 /// @version 0.0.1 15 /// @see <https://app.radicle.xyz/nodes/ash.radicle.garden/rad:z3YzABgyz2D36LiKe3YcdJ6PcDCXM/tree/README.md> 16 /// @see <https://moodle.ruc.dk/course/section.php?id=201713> 17 public class Roster { 18 19 /// Roster constructor 20 /// 21 /// Currently implemented only for 2D spaces, 22 /// but might in future support more dimensions. 23 /// 24 /// @param bottom Lowest corner of grid as integer array 25 /// @param top Highest corner of grid as integer array 26 public Roster(final int[] bottom, final int[] top) { 27 28 // store before validation, 29 // needed for Java final handling 30 this.BOTTOM = bottom; 31 this.TOP = top; 32 33 if (!Utils.fitsDimensions(BOTTOM) 34 || !Utils.fitsDimensions(TOP) 35 ) { 36 throw new IllegalArgumentException( 37 "2-dimensional coordinates required" 38 ); 39 } 40 41 this.LEN = new int[BOTTOM.length]; 42 for (int i = 0; i < BOTTOM.length; i++) { 43 LEN[i] = TOP[i] - BOTTOM[i] + 1; 44 } 45 46 this.places = new Object[LEN[0]][LEN[1]]; 47 } 48 49 /// Lowest position in grid 50 public final int[] BOTTOM; 51 52 /// highest position in grid 53 public final int[] TOP; 54 55 /// size of grid 56 public final int[] LEN; 57 58 /// Placement of thing 59 /// 60 /// A "thing" here means either an agent or an inanimate object. 61 /// 62 /// @param place position in grid 63 /// @param thing positioned thing 64 private record Object(Place place, Thing thing) { } 65 66 /// Physical position in grid 67 /// 68 /// Defined using record type to tersely define as immutable object 69 /// 70 /// @param coords coordinates as integer array of each Dimension 71 record Place(int[] coords) { 72 73 /// Input validation 74 /// 75 /// Ensure uniform array size compatible with grid. 76 /// 77 /// This simplifies (and optimizes) internal calculations 78 /// by not needing array length check at each computation 79 /// 80 /// @throws IllegalArgumentException must fit Dimension set 81 Place { 82 if (coords == null || coords.length != 2) { 83 throw new IllegalArgumentException( 84 "2-dimensional coordinates required" 85 ); 86 } 87 } 88 } 89 90 /// Get score 91 /// 92 /// @return score as integer 93 public final int getScore() { 94 return score; 95 } 96 97 /// collection of uniquely occupied places in the grid 98 private Object[][] places; 99 100 /// initialize random number generator 101 private static Random rand = new Random(); 102 103 /// player score 104 private int score; 105 106 /// Place a thing at a specific place in the grid if not occupied 107 /// 108 /// @param thing the thing to place 109 /// @param pos the position to place it as integer array 110 /// @return success of placement as boolean 111 public final boolean add(final Thing thing, final int[] pos) { 112 if (places[pos[0]][pos[1]] != null) { 113 return false; 114 } 115 places[pos[0]][pos[1]] = new Object(new Place(pos), thing); 116 return true; 117 } 118 119 // TODO: avoid risk of endless loop in too crowded space: 120 // a) count attempts and bail when exceeding a fair amount 121 // b) locate and only pick among vacant spots, 122 // and bail when none are left 123 /// Place a thing at a random empty place in the grid 124 /// 125 /// @param thing the thing to place in form of a character 126 /// @return success of placement as boolean 127 public boolean add(final Thing thing) { 128 int x; 129 int y; 130 while (true) { 131 x = rand.nextInt(LEN[0] - 1); 132 y = rand.nextInt(LEN[1] - 1); 133 if (places[x][y] == null) { 134 places[x][y] = new Object( 135 new Place(new int[] {x, y}), 136 thing 137 ); 138 break; 139 } 140 } 141 return true; 142 } 143 144 /// Get a move of an agent 145 /// 146 /// Resolve resulting position of one move by some agent, 147 /// wit current bearing of the agent. 148 /// An agent is a player, but can in future e extended to cover 149 /// opponents or scouting devices 150 /// (scouting is relevant for larger caves 151 /// and for gameplay with knowledge of places fading over time). 152 /// 153 /// @param agent the agent 154 /// @param pos starting position of speculative move 155 /// @return new next position as integer array 156 public final int[] virtualMove(final Agent agent, final int[] pos) { 157 Bearing heading = agent.getHeading(); 158 return new int[] { 159 pos[0] + 1 * heading.i, 160 pos[1] + 1 * heading.j 161 }; 162 } 163 164 /// Compose a POSIX ASCII map of grid contents 165 /// 166 /// @return ASCII map as a rectangular multi-line string 167 public final String map() { 168 StringBuilder sb = new StringBuilder(); 169 170 for (int i = 0; i < LEN[0]; i++) { 171 for (int j = 0; j < LEN[1]; j++) { 172 if (places[i][j] == null) { 173 sb.append('.'); 174 } else { 175 sb.append( 176 places[i][j].thing().seeDress() 177 ); 178 } 179 180 // space-delimit horisontally 181 if (j < places[i].length - 1) { 182 sb.append(' '); 183 } 184 } 185 186 // POSIX-newline-delimit vertically 187 if (i < places.length - 1) { 188 sb.append('\n'); 189 } 190 } 191 return sb.toString(); 192 } 193 194 /* TODO 195 /// Compute a line of sight 196 /// 197 /// @param agent the agent viewing 198 /// @return visibility distance 199 public final int view(final Entity.Agent agent) { 200 int[] pos = Utils.deltaPosition(agent.place().coords, BOTTOM); 201 int dir = agent.bearing.vector[agent.bearing.axis]; 202 int[] range = (dir > 0) 203 ? Utils.deltaPosition(TOP, agent.place().coords) 204 : Utils.deltaPosition(agent.place().coords, BOTTOM); 205 int i = 1; 206 207 // TODO: avoid hardcoded height dimension mirroring 208 final int flip = -1; 209 210 // count in each case from observation point towards an object 211 // TODO: make more DRY 212 switch (agent.bearing) { 213 case NORTH: 214 for (; i <= range[1]; i++) { 215 if (places[pos[1] + i * dir * flip][pos[0]] != null) { 216 return i; 217 } 218 } 219 break; 220 case EAST: 221 for (; i <= range[0]; i++) { 222 if (places[pos[1]][pos[0] + i * dir] != null) { 223 return i; 224 } 225 } 226 break; 227 case SOUTH: 228 for (; i <= range[1]; i++) { 229 if (places[pos[1] + i * dir * flip][pos[0]] != null) { 230 return i; 231 } 232 } 233 break; 234 case WEST: 235 for (; i <= range[0]; i++) { 236 if (places[pos[1]][pos[0] + i * dir] != null) { 237 return i; 238 } 239 } 240 break; 241 242 // we should never experience an alien bearing enum 243 default: 244 throw new UnsupportedOperationException( 245 "Impossible direction: " + agent.bearing.value); 246 } 247 248 // return distance to horizon (or "ice wall") 249 // @see <https://wiki.tfes.org/The_Ice_Wall> 250 return i; 251 } 252 */ 253 }