# 2 player text artillery game The objective of this little piece of code was to play with classes some more, whilst doing something ‘fun’. There is indeed much better ways to code the same piece, ie working the bearing could possibly be done easier using modulo % and that might (would) reduce the giant switch statement. Also, there is no error handling, no validation of inputs, it’s just a straight hacky piece, however, the code is reusable so it wouldn’t take much to improve or to add new bits and pieces – ie if you wanted to fire multiple rounds at once, or add additional attributes such as weather it would be quite easy.

What does it do? Enter bearing and range to target, if you get within (x) metres, it’s a kill. If not, the bearing and range from the round location to the target, is sent back to you, using this information you need to compute your next shot (hint: search for artillery bracketing method). Of course, your opponent can use this information to their benefit too – but just pretendsie they’re advantage comes from having an AN TPQ 36

## The Code – Walkthrough

### Set it up

```import random import math # Set map extents BOUNDARY_SW = [100.0, 100.0] BOUNDARY_NE = [1000.0, 1000.0] DEATH_RANGE = 10.0```

This first bit just sets up the constants and imports a couple of required libraries.

### Game and Player

```# The game board class Game:     def __init__(self):         self.player_turn = 0         self.round_e = 0.0         self.round_n = 0.0         self.round_el = 0.0         self.players = [] # Define a player class Player:     def __init__(self):         self.position_e = random.randrange(BOUNDARY_SW, BOUNDARY_NE, 1)         self.position_n = random.randrange(BOUNDARY_SW, BOUNDARY_NE, 1)         self.position_el = 0.0```

`The Game class, simply stores attributes required in game. Whose turn is it, where did their round land? And an array players.  When the game starts, two instances of the Player class (holding nothing more than their coordinates for this game) are created and each instance is stored in the array so I can cycle through for updates, select the correct player throughout the game and so on. I could store them in a dictionary and utilise player names as well.`

### CalculateCoordinates

#### Set up the class

```# Do the math class CalculateCoordinates:     def __init__(self):         self.round_val = 4         self.working_coords = []         self.bearing = 0.0         self.distance_2d = 0.0         self.distance_3d = 0.0         self.delta_e = 0.0         self.delta_n = 0.0         self.delta_el = 0.0```

`We just establish some initial class attributes (<-- hopefully my terminology is correct at this point!) that will be used for calculations, and set them all to nothings.`

#### Seek Deltas

```def seek_deltas(self):         # difference between both sets of coordinates. if len(self.working_coords) > 1:             self.delta_e = self.working_coords - self.working_coords             self.delta_n = self.working_coords - self.working_coords             self.delta_el = self.working_coords - self.working_coords         else:             quit("Not enough coords dude.")```

This first method simply calculates the differences between provided eastings, northings, and elevations.

#### Create Deltas

```def create_deltas(self):         # Creates deltas from bearing and distance self.delta_e = self.distance_2d * (math.sin(math.radians(self.bearing)))         self.delta_n = self.distance_2d * (math.cos(math.radians(self.bearing)))```

When we don’t have two sets of coordinates, we must have a bearing and distance from one set of coordinates, and then we can create the deltas from this to later compute the second set of coordinates.

#### Create Coordinates

```def create_coordinates(self):         # Create new coords from deltas. new_coord = [self.working_coords + self.delta_e,\                   self.working_coords + self.delta_n,\                   self.working_coords]         self.working_coords.append(new_coord)```

See ^ we simply add the deltas. If one of the deltas is a minus, it will add the negative (ie, it’ll subtract it) so this is a pretty simple method.

#### Seek Bearing

```def seek_bearing(self):         # A great big switch statement to determine the correct direction. if self.delta_e == 0 and self.delta_n > 0:             self.bearing = 0.0         elif self.delta_e == 0 and self.delta_n < 0:             self.bearing = 180.0         elif self.delta_e > 0 and self.delta_n == 0:             self.bearing = 90.0         elif self.delta_e < 0 and self.delta_n == 0:             self.bearing = 270.0 # Check for 45 degree variations. elif abs(self.delta_e) == abs(self.delta_n):             if self.delta_e > 0 and self.delta_n > 0:                 self.bearing = 45.0             elif self.delta_e > 0 > self.delta_n:                 self.bearing = 135.0             elif self.delta_e < 0 and self.delta_n < 0:                 self.bearing = 225.0             elif self.delta_n > 0 > self.delta_e:                 self.bearing = 315.0         # Compute it out. elif self.delta_e > 0:             self.bearing = math.degrees(math.atan(self.delta_e / self.delta_n))         elif self.delta_e < 0 and self.delta_n < 0:             self.bearing = math.degrees(math.atan(self.delta_e / self.delta_n)) + 180         elif self.delta_n > 0 > self.delta_e:             self.bearing = math.degrees(math.atan(self.delta_e / self.delta_n)) + 360 # The final piece. if self.bearing < 0:             self.bearing += 180```

here’s the giant switch statement :/ Given two sets of coordinates, it is simple math to find the bearing, but firstly you need to see if any math is actually required. If the deltas are the same (▲E: 500, ▲N: 500) then the horizontal bearing has to be in increments of 45°. If there is zero movement on one axis (▲E: 0.00, ▲N: 1000) then the bearing is in increments of 90° – so checking for those easy possibilities first, leaves us with some very simple math at the end to determine our final corrected bearing.

#### Distance 2D and Distance 3D

```def dist_2d(self): # Determine the distance. (2D) self.distance_2d = math.sqrt((self.delta_e ** 2 + self.delta_n ** 2)) def dist_3d(self): # Determine the distance. (3D) self.distance_3d = math.sqrt(self.delta_e ** 2 + self.delta_n ** 2 + self.delta_el ** 2)```

Distances are easy, you remember Pythagoras? Well that’s all that is going on here. Twice for the 3D distance (which isn’t used as I decided not to use elevations in this yet)

#### List the Coordinates

```def list_coords(self):         # Send back the values. for i in self.working_coords:             print("E: {e} N: {n} El: {el} ".format(                 e=round(i, self.round_val),                 n=round(i, self.round_val),                 el=round(i, self.round_val),             ))```

Real simple method to list the coordinates, not used except when I was writing the code and testing it out.

#### BDC and CBD

```def bdc(self):         # Return bearing and distance derived from two sets of coordinates. self.seek_deltas()         self.seek_bearing()         self.dist_2d()         self.dist_3d()     def cbd(self):         self.create_deltas()         self.create_coordinates())```

The last two methods use the previous methods to calculate either Bearing and Distance from Coordinates, or, Coordinates from Bearing and Distance. Remembering, BDC requires two sets of coordinates, CBD requires one set and a bearing and a distance.

### The Game Code

So now we have the stuff we need for the game, let’s put it together. The below code can be optimised, i think there’s a couple of breaches of DRY in there as well as a complete lack of validation / error checking.

```new_game = Game() calcs = CalculateCoordinates() number_players = 2 play_away = True while number_players:     new_game.players.append(Player())     number_players -= 1```

Instantiate an instance of the Game,  and CalculateCoordinates, and create two players – dropping each one in tho the Game players array (right at the start)

```while play_away:     calcs.working_coords = []     player_now = new_game.player_turn     if player_now == 0:         player_next = player_now + 1     else:         player_next = player_now - 1     print("player turn " + str(new_game.player_turn + 1))     # new_game.player_turn = player_next player_now_coords = []     player_next_coords = []     player_now_coords.append(new_game.players[player_now].position_e)     player_now_coords.append(new_game.players[player_now].position_n)     player_now_coords.append(new_game.players[player_now].position_el)     # print(player_now_coords)     # Calculate round fall. fire_bearing = float(input("fire at bearing: "))     fire_range = float(input("range: "))     calcs.distance_2d = fire_range     calcs.bearing = fire_bearing     calcs.working_coords.append(player_now_coords)```

Determine whose turn it is, and whose turn is next (who is firing, who is the target), set the working coordinates, take the bearing and range as inputs, store all of these in the CalculateCoordinates instance.

```calcs.cbd()     round_coords = [calcs.working_coords, calcs.working_coords, calcs.working_coords]     # Calculate bearing distance to target calcs.working_coords = []     calcs.working_coords.append(round_coords)     player_next_coords.append(new_game.players[player_next].position_e)     player_next_coords.append(new_game.players[player_next].position_n)     player_next_coords.append(new_game.players[player_next].position_el)     calcs.working_coords.append(player_next_coords)     calcs.bdc()```

Compute CBD (Coordinates from Bearing and Distance) from the previous inputs based on the firer coordinates and their input range and bearing, this will let us know where the round landed, from this, calculate BDC (Bearing and Distance from Coordinates) from the round landing position to the target position.

```    print("round landed: ")     print(str(round(calcs.distance_2d, 3)) + "m from target")     print("at bearing " + str(round(calcs.bearing, 2)))     if calcs.distance_2d <= DEATH_RANGE:         quit("CONGRATS YOU WON!")     else:         new_game.player_turn = player_next```

Wrap it up with some displayed text to help the user on their next turn, and do a check to see if they actually nailed the target or not.

Complete code below for a copy paste and play or use the github version to be sure of no format issues, let me know in the comments below or via Twitter (@hopBuddyHop) or Facebook if you liked this or have any questions. Plenty of improvements can be made, including play by net!

```import random import math # Set map extents BOUNDARY_SW = [100.0, 100.0] BOUNDARY_NE = [1000.0, 1000.0] DEATH_RANGE = 10.0 # The game board class Game:     def __init__(self):         self.player_turn = 0         self.round_e = 0.0         self.round_n = 0.0         self.round_el = 0.0         self.players = [] # Define a player class Player:     def __init__(self):         self.position_e = random.randrange(BOUNDARY_SW, BOUNDARY_NE, 1)         self.position_n = random.randrange(BOUNDARY_SW, BOUNDARY_NE, 1)         self.position_el = 0.0 # Do the math class CalculateCoordinates:     def __init__(self):         self.round_val = 4         self.working_coords = []         self.bearing = 0.0         self.distance_2d = 0.0         self.distance_3d = 0.0         self.delta_e = 0.0         self.delta_n = 0.0         self.delta_el = 0.0     def seek_deltas(self):         # difference between both sets of coordinates. if len(self.working_coords) > 1:             self.delta_e = self.working_coords - self.working_coords             self.delta_n = self.working_coords - self.working_coords             self.delta_el = self.working_coords - self.working_coords         else:             quit("Not enough coords dude.")     def create_deltas(self):         # Creates deltas from bearing and distance self.delta_e = self.distance_2d * (math.sin(math.radians(self.bearing)))         self.delta_n = self.distance_2d * (math.cos(math.radians(self.bearing)))     def create_coordinates(self):         # Create new coords from deltas. new_coord = [self.working_coords + self.delta_e,\                   self.working_coords + self.delta_n,\                   self.working_coords]         self.working_coords.append(new_coord)     def seek_bearing(self):         # A great big switch statement to determine the correct direction. if self.delta_e == 0 and self.delta_n > 0:             self.bearing = 0.0         elif self.delta_e == 0 and self.delta_n < 0:             self.bearing = 180.0         elif self.delta_e > 0 and self.delta_n == 0:             self.bearing = 90.0         elif self.delta_e < 0 and self.delta_n == 0:             self.bearing = 270.0         # Check for 45 degree variations. elif abs(self.delta_e) == abs(self.delta_n):             if self.delta_e > 0 and self.delta_n > 0:                 self.bearing = 45.0             elif self.delta_e > 0 > self.delta_n:                 self.bearing = 135.0             elif self.delta_e < 0 and self.delta_n < 0:                 self.bearing = 225.0             elif self.delta_n > 0 > self.delta_e:                 self.bearing = 315.0         # Compute it out. elif self.delta_e > 0:             self.bearing = math.degrees(math.atan(self.delta_e / self.delta_n))         elif self.delta_e < 0 and self.delta_n < 0:             self.bearing = math.degrees(math.atan(self.delta_e / self.delta_n)) + 180         elif self.delta_n > 0 > self.delta_e:             self.bearing = math.degrees(math.atan(self.delta_e / self.delta_n)) + 360         # The final piece. if self.bearing < 0:             self.bearing += 180     def dist_2d(self):         # Determine the distance. (2D) self.distance_2d = math.sqrt((self.delta_e ** 2 + self.delta_n ** 2))     def dist_3d(self):         # Determine the distance. (3D) self.distance_3d = math.sqrt(self.delta_e ** 2 + self.delta_n ** 2 + self.delta_el ** 2)     def list_coords(self):         # Send back the values. for i in self.working_coords:             print("E: {e} N: {n} El: {el} ".format(                 e=round(i, self.round_val),                 n=round(i, self.round_val),                 el=round(i, self.round_val),             ))     def bdc(self):         # Return bearing and distance derived from two sets of coordinates. self.seek_deltas()         self.seek_bearing()         self.dist_2d()         self.dist_3d()     def cbd(self):         self.create_deltas()         self.create_coordinates() new_game = Game() calcs = CalculateCoordinates() number_players = 2 play_away = True while number_players:     new_game.players.append(Player())     number_players -= 1 while play_away:     calcs.working_coords = []     player_now = new_game.player_turn     if player_now == 0:         player_next = player_now + 1     else:         player_next = player_now - 1     print("player turn " + str(new_game.player_turn + 1))     # new_game.player_turn = player_next player_now_coords = []     player_next_coords = []     player_now_coords.append(new_game.players[player_now].position_e)     player_now_coords.append(new_game.players[player_now].position_n)     player_now_coords.append(new_game.players[player_now].position_el)     # print(player_now_coords)     # Calculate round fall. fire_bearing = float(input("fire at bearing: "))     fire_range = float(input("range: "))     calcs.distance_2d = fire_range     calcs.bearing = fire_bearing     calcs.working_coords.append(player_now_coords)     calcs.cbd()     round_coords = [calcs.working_coords, calcs.working_coords, calcs.working_coords]     # Calculate bearing distance to target calcs.working_coords = []     calcs.working_coords.append(round_coords)     player_next_coords.append(new_game.players[player_next].position_e)     player_next_coords.append(new_game.players[player_next].position_n)     player_next_coords.append(new_game.players[player_next].position_el)     calcs.working_coords.append(player_next_coords)     calcs.bdc()     print("round landed: ")     print(str(round(calcs.distance_2d, 3)) + "m from target")     print("at bearing " + str(round(calcs.bearing, 2)))     if calcs.distance_2d <= DEATH_RANGE:         quit("CONGRATS YOU WON!")     else:         new_game.player_turn = player_next``` 