Python Forum
Robots! (A game using tkinter)
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Robots! (A game using tkinter)
#1
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. Big Grin 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. Wink

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()
Reply
#2
Not a bad game. I like it.
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags


Reply
#3
A beautiful game - I really enjoyed it
pyzyx3qwerty
"The greatest glory in living lies not in never falling, but in rising every time we fall." - Nelson Mandela
Need help on the forum? Visit help @ python forum
For learning more and more about python, visit Python docs
Reply
#4
Nice. Just played it. Took a while to get the hang off.
Reply
#5
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.
Reply
#6
(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.

Overall though, its a great start.

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.
Reply
#7
Well, good luck on your program development! Smile
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020