Robots! (A game using tkinter) - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: General (https://python-forum.io/forum-1.html) +--- Forum: Code Review (https://python-forum.io/forum-46.html) +--- Thread: Robots! (A game using tkinter) (/thread-27770.html) |
Robots! (A game using tkinter) - GOTO10 - Jun-20-2020 Hi Python Friends! This is my homage to (ripoff of) the classic computer game Chase that I completed a few weeks ago. The version of this that I knew and fell in love with as a high school student in the late 80s was Daleks. I made a prettier version for my nephew with sprites and sound effects, but I'm providing this version here since it requires nothing extra to execute. The gist of it is that the player (a blue circle in this version) is trying to wipe out the bots (red squares) by causing them to collide with each other and then leading the other bots into the debris (black squares). The controls are documented in the right-hand panel of the game, but here is how it works: The game is turn-based, and after each move by the player, the bots advance toward you using the most direct path possible (8-way movement). Use the numeric keypad to move. You get one "Zap" and one "Safe Port" per round. Zap kills all bots adjacent to the player instantly, and the safe teleport moves you to a random unoccupied square and skips the bots' turn. The regular teleport is unlimited, but it is truly random and the bots move after you teleport, so it's possible to die through bad luck when using the teleport. The "Last Stand" option just leaves the player in place and moves the bots until the round ends, either through all the bots dying or the player's death. This was written while I was attempting to figure out tkinter, and I am aware of several inefficiencies in the code that I haven't bothered to clean up. Among other things, I now understand that I could have taken advantage of tkinter frames to keep the game board and stats display separate, but I had no idea what frames were when I coded this. I'm also not sure why I ended up using methods of the Game class to call methods of the Player class, but that's what came out of my efforts to make things work. I welcome any and all criticism, but I am particularly interested in any suggestions that will help me think in a more object-oriented programming sort of way. I ended up using class variables instead of object/instance variables for a few things like score, round count, etc., but I am sure there is a better way of doing things. FYI, my personal high score is 4200. from tkinter import * import sys import random import time class Player: def __init__(self, canvas, x, y): self.canvas = canvas self.id = canvas.create_oval(0, 0, 20, 20, fill='blue') self.canvas.move(self.id, x, y) self.canvas_height = 600 self.canvas_width = 600 def move_left(self): pos = self.canvas.coords(self.id) if pos[0] > 0: self.canvas.move(self.id, -20, 0) bot_move(Game.bot_list) def move_up_left(self): pos = self.canvas.coords(self.id) if pos[0] > 0 and pos[1] > 0: self.canvas.move(self.id, -20, -20) bot_move(Game.bot_list) def move_down_left(self): pos = self.canvas.coords(self.id) if pos[0] > 0 and pos[3] < self.canvas_height: self.canvas.move(self.id, -20, 20) bot_move(Game.bot_list) def move_right(self): pos = self.canvas.coords(self.id) if pos[2] < self.canvas_width: self.canvas.move(self.id, 20, 0) bot_move(Game.bot_list) def move_up_right(self): pos = self.canvas.coords(self.id) if pos[2] < self.canvas_width and pos[1] > 0: self.canvas.move(self.id, 20, -20) bot_move(Game.bot_list) def move_down_right(self): pos = self.canvas.coords(self.id) if pos[2] < self.canvas_width and pos[3] < self.canvas_height: self.canvas.move(self.id, 20, 20) bot_move(Game.bot_list) def move_down(self): pos = self.canvas.coords(self.id) if pos[3] < self.canvas_height: self.canvas.move(self.id, 0, 20) bot_move(Game.bot_list) def move_up(self): pos = self.canvas.coords(self.id) if pos[1] > 0: self.canvas.move(self.id, 0, -20) bot_move(Game.bot_list) def wait(self): bot_move(Game.bot_list) def teleport(self): pos = self.canvas.coords(self.id) random_x = random.choice(list(range(0,581,20))) random_y = random.choice(list(range(0,581,20))) self.canvas.move(self.id, (random_x - pos[0]), (random_y - pos[1])) bot_move(Game.bot_list) def safe_port(self): range_x = list(range(0,581,20)) range_y = list(range(0,581,20)) range_tuples = [] for i in range_x: for j in range_y: new_tuple = (i,j) range_tuples.append(new_tuple) pos = self.canvas.coords(self.id) bot_pos = [] bot_ID_list = list(self.canvas.find_withtag('bot')) for i in bot_ID_list: i_pos = self.canvas.coords(i) bot_pos_tuple = (i_pos[0], i_pos[1]) bot_pos.append(bot_pos_tuple) safe_tuples = [] safe_tuples = [a for a in range_tuples if a not in bot_pos] safe_port_loc = random.sample(safe_tuples, 1) random_x = safe_port_loc[0][0] random_y = safe_port_loc[0][1] self.canvas.move(self.id, (random_x - pos[0]), (random_y - pos[1])) class Bot: def __init__(self, canvas, player, x, y): self.canvas = canvas self.player = player self.id = canvas.create_rectangle(0, 0, 20, 20, fill='red', tags='bot') self.start_x = x self.start_y = y self.dead = False self.canvas.move(self.id, self.start_x, self.start_y) def draw(self): self.x = 0 self.y = 0 pos = self.canvas.coords(self.id) player_pos = self.canvas.coords(self.player.id) if pos[0] >= player_pos[2]: self.x = -20 if pos[2] <= player_pos[0]: self.x = 20 if pos[1] >= player_pos[3]: self.y = -20 if pos[3] <= player_pos[1]: self.y = 20 self.canvas.move(self.id, self.x, self.y) def hit_player(self): pos = self.canvas.coords(self.id) player_pos = self.canvas.coords(self.player.id) if pos == player_pos: time.sleep(.35) self.canvas.create_text(400, 150, text = 'GAME', font = ('Arial', 100)) self.canvas.create_text(400, 300, text = 'OVER!', font = ('Arial', 100)) self.canvas.create_text(400, 475, text = 'Hit Enter to play again or X to exit.', font = ('Arial', 35)) Game.game_over = True return True def hit_bot(self): bot_ID_list = list(self.canvas.find_withtag('bot')) bot_ID_list.remove(self.id) pos = self.canvas.coords(self.id) for i in bot_ID_list: bot_pos = self.canvas.coords(i) if pos == bot_pos: self.canvas.itemconfig(self.id, fill='black') return True def bot_zapped(self, tuples): bot_pos = self.canvas.coords(self.id) bot_tuple = (bot_pos[0], bot_pos[1]) if bot_tuple in tuples: self.canvas.delete(self.id) return True return False class Game: bot_list = [] game_over = False score = 0 zap_count = 1 safeprt_count = 1 def __init__(self): self.tk = Tk() self.tk.title("Robots!") self.tk.resizable(0, 0) self.tk.wm_attributes("-topmost", 1) self.game_round = 1 self.round_over = False self.rt_id = None self.st_id = None self.zap_id = None self.start_round() self.tk.mainloop() def start_round(self): if self.round_over == True and self.canvas is not None: self.canvas.destroy() self.game_round += 1 self.rt_id = None self.st_id = None self.zap_id = None self.round_over = False self.canvas = Canvas(self.tk, width=800, height=600, bd=0, highlightthickness=0) self.canvas.create_rectangle(0, 0, 600, 600, fill='cyan') self.canvas.create_line(600, 0, 600, 600) Game.zap_count = 1 Game.safeprt_count = 1 self.display_stats() self.canvas.pack() self.canvas.bind_all('<KeyPress-Left>', self.player_move_left) self.canvas.bind_all('<KeyPress-4>', self.player_move_left) self.canvas.bind_all('<KeyPress-Home>', self.player_move_up_left) self.canvas.bind_all('<KeyPress-7>', self.player_move_up_left) self.canvas.bind_all('<KeyPress-End>', self.player_move_down_left) self.canvas.bind_all('<KeyPress-1>', self.player_move_down_left) self.canvas.bind_all('<KeyPress-Right>', self.player_move_right) self.canvas.bind_all('<KeyPress-6>', self.player_move_right) self.canvas.bind_all('<KeyPress-Prior>', self.player_move_up_right) self.canvas.bind_all('<KeyPress-9>', self.player_move_up_right) self.canvas.bind_all('<KeyPress-Next>', self.player_move_down_right) self.canvas.bind_all('<KeyPress-3>', self.player_move_down_right) self.canvas.bind_all('<KeyPress-Up>', self.player_move_up) self.canvas.bind_all('<KeyPress-8>', self.player_move_up) self.canvas.bind_all('<KeyPress-Down>', self.player_move_down) self.canvas.bind_all('<KeyPress-2>', self.player_move_down) self.canvas.bind_all('<KeyPress-F3>', self.player_wait) self.canvas.bind_all('<KeyPress-F1>', self.player_safe_port) self.canvas.bind_all('<KeyPress-F2>', self.player_teleport) self.canvas.bind_all('<KeyPress-space>', self.player_zap) self.canvas.bind_all('<KeyPress-Return>', self.restart_round) self.canvas.bind_all('<KeyPress-X>', self.end_game) self.canvas.bind_all('<KeyPress-x>', self.end_game) self.canvas.bind_all('<KeyPress-z>', self.last_stand) self.canvas.bind_all('<KeyPress-Z>', self.last_stand) self.bot_count = self.game_round * 5 Game.bot_list = [] range_x = list(range(0,581,20)) range_y = list(range(0,581,20)) range_tuples = [] for i in range_x: for j in range_y: new_tuple = (i,j) range_tuples.append(new_tuple) random_tuple = random.sample(range_tuples, self.bot_count + 1) self.player = Player(self.canvas, random_tuple[0][0], random_tuple[0][1]) Game.bot_list = [Bot(self.canvas, self.player, random_tuple[i + 1][0], \ random_tuple[i + 1][1]) for i in range(0, self.bot_count)] def player_move_left(self, evt): if Game.game_over == False: self.player.move_left() self.advance_turn() def player_move_up_left(self, evt): if Game.game_over == False: self.player.move_up_left() self.advance_turn() def player_move_down_left(self, evt): if Game.game_over == False: self.player.move_down_left() self.advance_turn() def player_move_right(self, evt): if Game.game_over == False: self.player.move_right() self.advance_turn() def player_move_up_right(self, evt): if Game.game_over == False: self.player.move_up_right() self.advance_turn() def player_move_down_right(self, evt): if Game.game_over == False: self.player.move_down_right() self.advance_turn() def player_move_up(self, evt): if Game.game_over == False: self.player.move_up() self.advance_turn() def player_move_down(self, evt): if Game.game_over == False: self.player.move_down() self.advance_turn() def player_wait(self, evt): if Game.game_over == False: self.player.wait() self.advance_turn() def player_teleport(self, evt): self.player.teleport() self.advance_turn() def player_zap(self, evt): if Game.game_over == False and Game.zap_count > 0: Game.zap_count -= 1 player_pos = self.canvas.coords(self.player.id) touching_x = [(player_pos[0] - 20), player_pos[0], (player_pos[0] + 20)] touching_y = [(player_pos[1] - 20), player_pos[1], (player_pos[1] + 20)] touching_tuples = [] for i in touching_x: for j in touching_y: new_tuple = (i, j) touching_tuples.append(new_tuple) for i in Game.bot_list: if i.dead == False and i.bot_zapped(touching_tuples) == True: i.dead = True Game.score += 20 self.advance_turn() def player_safe_port(self, evt): if Game.game_over == False and Game.safeprt_count > 0: Game.safeprt_count -= 1 self.player.safe_port() self.advance_turn() def last_stand(self, evt): self.wait_cycle() def wait_cycle(self): if Game.game_over == False and self.round_over == False: self.player.wait() self.tk.update() time.sleep(.15) if round_end(Game.bot_list) == True: self.round_over = True Game.score += 50 self.start_round() return elif Game.game_over == False and self.round_over == False: self.tk.after(1, self.wait_cycle()) def advance_turn(self): if round_end(Game.bot_list) == True: self.round_over = True Game.score += 50 self.start_round() self.display_stats() def display_stats(self): round_text = 'Round: ' + str(self.game_round) score_text = 'Score: ' + str(Game.score) zaps_text = 'Zaps: ' + str(Game.zap_count) ports_text = 'Safe ports: ' + str(Game.safeprt_count) instruction_text = 'Instructions:\nMove using keypad\n<F1> Safe teleport\n<F2> Random teleport\n<F3> Wait one turn\n<Space> Zap adjacent bots\n<Z> Last Stand' if self.rt_id is not None and self.st_id is not None and\ self.zap_id is not None and self.sp_id is not None: self.canvas.itemconfig(self.rt_id, text = round_text) self.canvas.itemconfig(self.st_id, text = score_text) self.canvas.itemconfig(self.zap_id, text = zaps_text) self.canvas.itemconfig(self.sp_id, text = ports_text) else: self.rt_id = self.canvas.create_text(700, 100, text = round_text, font = ('Arial', 15)) self.st_id = self.canvas.create_text(700, 150, text = score_text, font = ('Arial', 15)) self.zap_id = self.canvas.create_text(700, 250, text = zaps_text, font = ('Arial', 15)) self.sp_id = self.canvas.create_text(700, 300, text = ports_text, font = ('Arial', 15)) self.instruct = self.canvas.create_text(700, 400, text = instruction_text, justify='center', font = ('Arial', 10)) def end_game(self, evt): if Game.game_over == True: self.tk.destroy() sys.exit() def restart_round(self, evt): if Game.game_over == True: self.rt_id = None self.game_round = 1 self.canvas.destroy() Game.bot_list = [] Game.game_over = False Game.score = 0 Game.zap_count = 1 Game.safeprt_count = 1 self.start_round() def bot_move(bot_list): for i in bot_list: if i.dead == False: i.draw() for i in bot_list: if i.hit_player() == True: Game.game_over = True if i.dead == False and i.hit_bot() == True: Game.score += 20 i.dead = True def round_end(bot_list): bots_alive = False for i in bot_list: if i.dead == False: bots_alive = True if bots_alive == True: return False return True Game() RE: Robots! (A game using tkinter) - menator01 - Jun-21-2020 Not a bad game. I like it. RE: Robots! (A game using tkinter) - pyzyx3qwerty - Jun-22-2020 A beautiful game - I really enjoyed it RE: Robots! (A game using tkinter) - Knight18 - Jun-22-2020 Nice. Just played it. Took a while to get the hang off. RE: Robots! (A game using tkinter) - BitPythoner - Jul-16-2020 Good job! Though you should adjust the game over tag not to overlap the scoring tab. You should also make the game more fluid, where the user moves instead of teleports to squares, additionally, why don't we see the player actually disappear? Finally, the zap function does not work. Overall though, its a great start. RE: Robots! (A game using tkinter) - GOTO10 - Jul-16-2020 (Jul-16-2020, 02:15 PM)BitPythoner Wrote: Good job! Though you should adjust the game over tag not to overlap the scoring tab. You should also make the game more fluid, where the user moves instead of teleports to squares, additionally, why don't we see the player actually disappear? Finally, the zap function does not work. Thanks, I appreciate the input! I do plan to someday (maybe not soon) rewrite this and make several cosmetic changes, including fixing the game over message issue you mentioned. I never thought of smoothing out the animation as players and bots move from square to square on the grid, but that is an interesting idea that could be worth implementing. I also want to make the teleport function look more interesting by having the player fade out on the current square and then fade in on the new square. The zap function works as intended, as far as I can tell. It is meant to eliminate any bots that are directly adjacent to (touching) the player when the zap is triggered, and it does not leave debris behind. If that's not what is happening for you, let me know what is happening instead. The one bug that I do know exists in this version is that you can still use the random teleport after dying. I accidentally removed the check for Game.game_over from that function when I was cleaning up the comments and etc. RE: Robots! (A game using tkinter) - BitPythoner - Jul-17-2020 Well, good luck on your program development! |