Two player Artillery shooter
This is a neat little game I wrote to demonstrate one particular way to use the survey-toolbox. I have a comprehensive code walkthrough below, and right at the bottom is the full code you can paste into a python file (.py) and you're good to go.
The concept of this game is pretty simple, two players are created at random points on a 'map' - in this case a predetermined game grid. The players have 10 rounds each with which to zero in and kill the opponent.
They're not flying blind though, I have provided the courtesy of a gun computer that will give an estimate of the bearing and distance to the target - it's not accurate so don't rely on it to save your life! Take notes, use your head - get rounds on target before your enemy finds you!
This is not the first time I've built this game (I've coded a few Python versions, and at least one in Java) but it is the first time I've built it using the survey-toolbox. It was great to use this because it:
- Made it so much faster to code and
- Highlighted a few bugs in the survey-toolbox for me to get to fixing straight away for a v 0.0.4 release.
As far as games go, this is pretty simple, you could definitely tweak it a bit more if you wanted, some suggestions are:
- Add damage to the players, the closer you are the more damage you do. Currently it's a straight up kill within 100m
- Add inaccuracies to the players, a little drift on the projectiles wouldn't kill anybody (ha, great pun!)
- Really go all out and add a web frontend to it. This game is text based, but there is no reason at all the same calculations couldn't be used in a web based game (by for example; tacking it into a Django setup, or, running it on its own server and exporting coordinates in JSON format via API)
- Add some bonus targets randomly within the gamespace, if you hit them - you might get better ammunition, more lives etc.
One last thing before we get into it, I'm hoping to show you next week how to implement the survey-toolbox in an ArcGIS environment so make sure you subscribe for that!
pip install survey-toolbox
from surveytoolbox.config import EASTING, NORTHING, ELEVATION, BEARING, DIST_2D
from surveytoolbox.bdc import bearing_distance_from_coordinates
from surveytoolbox.cbd import coordinates_from_bearing_distance
from surveytoolbox.PointStore import NewPointStore
from surveytoolbox.SurveyPoint import NewSurveyPoint
from surveytoolbox.dms import to_deg_min_sec
from random import uniform
# Set the bounds of our game.
GAME_MIN_E = 1000
GAME_MAX_E = 10000
GAME_MIN_N = 1000
GAME_MAX_N = 10000
GAME_MIN_EL = 250
GAME_MAX_EL = 300
# We'll round to x decimals just for the hell of it.
ROUND_TO_DEC = 3
# Set the kill zone of our Artillery rounds in metres
KILL_ZONE = 100
# Set the players - you can choose to do this based on user input if you prefer.
PLAYER_1 = "player 1"
PLAYER_2 = "player 2"
PLAYERS = [PLAYER_1, PLAYER_2]
def calc_errors(err_percent, original_value):
"""Calculates and returns a value with an error."""
calc_error = original_value - original_value * err_percent
calc_lower = - calc_error / 2
calc_upper = calc_error / 2
calc_error = uniform(calc_lower, calc_upper)
return original_value + calc_error
# Set the error of our estimations
error_bg = 0.95
error_dist = 0.95
# Create a point store so we can store and retrieve our points. (or create a dictionary / array / connect to db instead)
game_players = NewPointStore()
# Create a new point object for each player and drop it in the point store.
for x in PLAYERS:
game_players.set_new_point(NewSurveyPoint(x))
# TODO should not be accessing the point store directly.
# Assign some random coordinates, keeping it within our game boundaries.
game_players.point_store[x].set_vertex(
{
EASTING: round(uniform(GAME_MIN_E, GAME_MAX_E), ROUND_TO_DEC),
NORTHING: round(uniform(GAME_MIN_N, GAME_MAX_N), ROUND_TO_DEC),
ELEVATION: round(uniform(GAME_MIN_EL, GAME_MAX_EL), ROUND_TO_DEC)
}
)
# Let's make the game run in a loop
game_on = True
player_turn = PLAYER_1
next_player = PLAYER_2
round_number = 1
playable_rounds = 10
while game_on and round_number < playable_rounds:
# Whose turn is it?
print(f"it is {player_turn}'s turn")
# get an approximate bearing / distance from player to player
# TODO build an error in here
bd_p_to_p = bearing_distance_from_coordinates(
game_players.point_store[player_turn].get_vertex(),
game_players.point_store[next_player].get_vertex()
)
approx_dist = int(calc_errors(error_dist, bd_p_to_p[DIST_2D]))
approx_bg = int(calc_errors(error_bg, bd_p_to_p[BEARING]))
# don't forget to convert decimal degrees to dms before formatting it!
bearing_dms = round(to_deg_min_sec(bd_p_to_p[BEARING]), 4)
print(f"you are {approx_dist}m at bearing {approx_bg}°")
# create a temp array to store the input bearing and distance.
bd_to_tgt = []
for y in ["bearing", "distance"]:
bd_to_tgt.append(float(input(f"please enter a {y} to target: ")))
print("firing!")
# Determine where the round lands.
splash = coordinates_from_bearing_distance(
game_players.point_store[player_turn].get_vertex(),
bd_to_tgt[0],
bd_to_tgt[1]
)
# Determine distance from splash to target
did_we_hit = bearing_distance_from_coordinates(splash, game_players.point_store[next_player].get_vertex())
# Check for win condition. Exit game or switch to next player.
if did_we_hit[DIST_2D] < KILL_ZONE:
print("=============================================")
print(f"{player_turn} just destroyed {next_player}!")
game_on = False
exit("win condition!")
print("=============================================")
else:
print(f"{player_turn}'s round landed within {round(did_we_hit[DIST_2D], 0)} metres of {next_player}...")
print("=============================================")
if player_turn == PLAYER_1:
player_turn = PLAYER_2
next_player = PLAYER_1
else:
player_turn = PLAYER_1
next_player = PLAYER_2
# End of round
round_number += 1
# Reduce the errors just a little
if error_dist < 1:
error_dist = error_dist * 1.01
if error_bg < 1:
error_bg = error_bg * 1.01
And that - my pals, is a simple text based two player Python game written utilising my survey-toolbox. I'm quite chuffed really :D Full code below, feedback always welcome.
from surveytoolbox.config import EASTING, NORTHING, ELEVATION, BEARING, DIST_2D
from surveytoolbox.bdc import bearing_distance_from_coordinates
from surveytoolbox.cbd import coordinates_from_bearing_distance
from surveytoolbox.PointStore import NewPointStore
from surveytoolbox.SurveyPoint import NewSurveyPoint
from surveytoolbox.dms import to_deg_min_sec
from random import uniform
# Set the bounds of our game.
GAME_MIN_E = 1000
GAME_MAX_E = 10000
GAME_MIN_N = 1000
GAME_MAX_N = 10000
GAME_MIN_EL = 250
GAME_MAX_EL = 300
# We'll round to x decimals just for the hell of it.
ROUND_TO_DEC = 3
# Set the kill zone of our Artillery rounds in metres
KILL_ZONE = 100
# Set the players - you can choose to do this based on user input if you prefer.
PLAYER_1 = "player 1"
PLAYER_2 = "player 2"
PLAYERS = [PLAYER_1, PLAYER_2]
def calc_errors(err_percent, original_value):
"""Calculates and returns a value with an error."""
calc_error = original_value - original_value * err_percent
calc_lower = - calc_error / 2
calc_upper = calc_error / 2
calc_error = uniform(calc_lower, calc_upper)
return original_value + calc_error
# Set the error of our estimations
error_bg = 0.95
error_dist = 0.95
# Create a point store so we can store and retrieve our points. (or create a dictionary / array / connect to db instead)
game_players = NewPointStore()
# Create a new point object for each player and drop it in the point store.
for x in PLAYERS:
game_players.set_new_point(NewSurveyPoint(x))
# TODO should not be accessing the point store directly.
# Assign some random coordinates, keeping it within our game boundaries.
game_players.point_store[x].set_vertex(
{
EASTING: round(uniform(GAME_MIN_E, GAME_MAX_E), ROUND_TO_DEC),
NORTHING: round(uniform(GAME_MIN_N, GAME_MAX_N), ROUND_TO_DEC),
ELEVATION: round(uniform(GAME_MIN_EL, GAME_MAX_EL), ROUND_TO_DEC)
}
)
# Let's make the game run in a loop
game_on = True
player_turn = PLAYER_1
next_player = PLAYER_2
round_number = 1
playable_rounds = 10
while game_on and round_number < playable_rounds:
# Whose turn is it?
print(f"it is {player_turn}'s turn")
# get an approximate bearing / distance from player to player
# TODO build an error in here
bd_p_to_p = bearing_distance_from_coordinates(
game_players.point_store[player_turn].get_vertex(),
game_players.point_store[next_player].get_vertex()
)
approx_dist = int(calc_errors(error_dist, bd_p_to_p[DIST_2D]))
approx_bg = int(calc_errors(error_bg, bd_p_to_p[BEARING]))
# don't forget to convert decimal degrees to dms before formatting it!
bearing_dms = round(to_deg_min_sec(bd_p_to_p[BEARING]), 4)
print(f"you are {approx_dist}m at bearing {approx_bg}°")
# create a temp array to store the input bearing and distance.
bd_to_tgt = []
for y in ["bearing", "distance"]:
bd_to_tgt.append(float(input(f"please enter a {y} to target: ")))
print("firing!")
# Determine where the round lands.
splash = coordinates_from_bearing_distance(
game_players.point_store[player_turn].get_vertex(),
bd_to_tgt[0],
bd_to_tgt[1]
)
# Determine distance from splash to target
did_we_hit = bearing_distance_from_coordinates(splash, game_players.point_store[next_player].get_vertex())
# Check for win condition. Exit game or switch to next player.
if did_we_hit[DIST_2D] < KILL_ZONE:
print("=============================================")
print(f"{player_turn} just destroyed {next_player}!")
game_on = False
exit("win condition!")
print("=============================================")
else:
print(f"{player_turn}'s round landed within {round(did_we_hit[DIST_2D], 0)} metres of {next_player}...")
print("=============================================")
if player_turn == PLAYER_1:
player_turn = PLAYER_2
next_player = PLAYER_1
else:
player_turn = PLAYER_1
next_player = PLAYER_2
# End of round
round_number += 1
# Reduce the errors just a little
if error_dist < 1:
error_dist = error_dist * 1.01
if error_bg < 1:
error_bg = error_bg * 1.01