#!/usr/bin/env python3 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. """Common "newbie program", a game to destroy opponents Done purely for example purposes to introduce others to Object Oriented Programing """ __all__ = ['BadDirectionError', 'Bot', 'OutOfFieldError', 'Position', 'PositionError', 'main', 'menu'] __author__ = "Cody A. Taylor ( codmister99@yahoo.com )" __version__ = "2.1.2" import math from random import randint from CodeLIB import get_int def main(): """Example run of a game Normally I would not include all the menu options, as it would make it too easy for the player. I would likely remove the "distance from start" because it is not helpful and overlaps with "position". """ print("Welcome to Battle Bots!", "Objective: Find and kill enemy", sep="\n") bot = Bot() enemy = Position(randint(-30, 30), randint(-30, 30)) menu_options = ["Move", "Fire", "Fuel Used", "Distance From Start", "Position", "Help", "Exit"] while True: option = menu(menu_options) if option == "move": print("Possible directions are North, South, NE, SW, etc") direction = input("What direction would you like to try? ").strip() distance = get_int("How far would you like to move", "? ", name="distance") before_move = bot.distance_from_enemy(enemy) try: bot.move(distance, direction) if bot.distance_from_enemy(enemy) < before_move: print("You have moved closer to the Enemy") else: print("You have moved away from they Enemy") except PositionError as err: print(err) continue if bot.fuel < 1: print("You have run out of fuel!") break elif option == "fire": distance = bot.distance_from_enemy(enemy) if distance > 20: print("The Enemy is very far away") elif distance > 12: print("The Enemy is almost within striking distance") else: print("You have killed the enemy! A job well done!") break bot.fire_weapon() if bot.ammo == 0: print("You ran out of ammo!") break elif option == "fuel used": print(bot.fuel_tank()) elif option == "distance from start": print(bot.traveled("feet")) elif option == "position": print(bot) elif option == "help": print("Game Play:", " Move the bot, firing when you think the Enemy is close", " If you run out of Fuel or Ammo the game is over", " ", "Command Help:", " Move - move the bot, hopefully closer to the Enemy", " Fire - attempt to kill the bot, use to if your in striking distance", " Fuel Used - see how much fuel you have", " Distance from start - see if you've actually gone no-where", " Help - print this message", " Exit - stop play and leave the game", sep="\n") elif option == "exit": print("Giving Up?") break class Position(object): """Plot a point on a X-Y coordinate I use it for the enemy bot since I don't need it to move or return fire """ def __init__(self, x=0.0, y=0.0): self.x = float(x) self.y = float(y) def __repr__(self): return "Position({0.x!r}, {0.y!r})".format(self) def __str__(self): return "Position({0.x!r}, {0.y!r})".format(self) def __eq__(self, other): return self.x == other.x and self.y == other.y def __lt__(self, other): return self.x < other.x and self.y < other.y def __le__(self, other): return self.x <= other.x and self.y <= other.y def __add__(self, other): self.x = self.x + other.x self.y = self.x + other.y return self def __iadd__(self, other): self.x += other.x self.y += other.y return self def __sub__(self, other): self.x = self.x - other.x self.y = self.y - other.y return self def __isub__(self, other): self.x -= other.x self.y -= other.y return self def distance_from_origin(self): return math.hypot(self.x, self.y) def distance_between_points(self, other): return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2) class PositionError(Exception): pass class OutOfFieldError(PositionError): pass class BadDirectionError(PositionError): pass class Bot(Position): """Adds ammo and fuel data to Position This way the bot is well described """ def __init__(self, x=0.0, y=0.0, ammo=5, fuel=60.0, fuel_rate=1.0): super().__init__(x, y) self.ammo = int(ammo) self.fuel = float(fuel) self.full_tank = int(fuel) self.fuel_rate = float(fuel_rate) def distance_from_enemy(self, other): return super().distance_between_points(other) def fire_weapon(self): self.ammo -= 1 return self def traveled(self, units="feet"): """Return a string stating how far the bot is from Position(0, 0) """ return "The bot is {this_far} {0} away from the starting position".format(units, this_far=int(self.distance_from_origin())) def move(self, distance, direction="North", field_range=30): """Move your position in compass directions This will also use fuel if no Errors are raised Will raise BadDirectionError if direction doesn't make sense Will raise OutOfFieldError if location is out of range (field_range argument) """ direction = direction.lower() try_move = Position(self.x, self.y) if direction in {"n", "north"}: try_move.y += distance # move up on line x = y-intercept elif direction in {"s", "south"}: try_move.y -= distance # move down on line x = y-intercept elif direction in {"e", "east"}: try_move.x += distance # move right on line y = x-intercept elif direction in {"w", "west"}: try_move.x -= distance # move left on line y = x-intercept elif direction in {"ne", "northeast", "sw", "southwest"}: distance = distance / math.sqrt(2) if direction in {"ne", "northeast"}: try_move += Position(distance, distance) # move up on line f(x) = x else: try_move -= Position(distance, distance) # move down on line f(x) = x elif direction in {"nw", "northwest", "se", "southeast"}: distance = distance / math.sqrt(2) if direction in {"nw", "northwest"}: try_move += Position(distance, (distance * -1)) # move up on line f(x) = -x else: try_move -= Position(distance, (distance * -1)) # move down on line f(x) = -x else: raise BadDirectionError("{0} is a invalid direction".format(direction)) if not (Position(-1 * field_range, -1 * field_range) <= try_move <= Position(field_range, field_range)): raise OutOfFieldError("You are trying to move out the playing field, the max is 30 feet from the start position") self.x, self.y = try_move.x, try_move.y self.fuel -= (distance / self.fuel_rate) return self def fuel_tank(self): """Returns a string that appears like a text fuel tank >>> fuel_tank() '[=============================================---------------]' """ return "[{0:-<{1}}]".format("=" * int(self.fuel), self.full_tank) def menu(options): """Print a menu and return the players valid choice A input() pause is put at the start to allow the player to go at their own pace, but this does get annoying on longer games. """ input("\nHit Enter...") while True: print("\nMenu:", *options, sep="\n ") line = input("\nWhat would you like to do? ").lower().strip() if not line in {x.lower() for x in options}: print("Error: {0} is not an option".format(line)) continue else: print() return line if __name__ == "__main__": main()