Tank Battle
This game is a two-player turn-based artillery game where players adjust angle and power to fire shells over terrain and destroy the opposing tank.
Introduction
Tank Battle is a turn-based artillery game inspired by classic tank duels, where two players take turns adjusting the angle and power of their cannon to fire shells across a procedurally generated landscape. The goal is to land a direct hit on the opponentβs tank, while accounting for terrain height and gravity. With its pixel-style graphics, sound effects, and dynamic terrain, each round becomes a fun and strategic contest of aim, timing, and precision.
The game is operated using a simple set of button controls. Players press the Start button to begin the game, then take turns using the B button to switch between angle and power modes. While in either mode, the Up and Down directional inputs adjust the cannonβs firing parameters. Once the shot is lined up, pressing the A button launches the shell. Realistic physics animate the projectileβs path, and the system checks for hits against the terrain or enemy tank.
After a tank is destroyed, a celebratory victory screen and music appear, declaring the winner. Players can then press the Start or B button to reset the battlefield and begin a new match.
Programming Design

Source Code
By default, your device comes with the necessary firmware and libraries preinstalled. If you have modified or replaced the firmware or libraries, make sure the following files are present (download if need by click the link)
Below is the main program file along with any additional files required for this specific app.
Main.py
import math
import random
import utime
import test.st7789 as welcome
import test.st7789 as st7789
import sys
print(sys.implementation)
from machine import Timer
from tanks.musicPlay import musicPlay
from tanks.tank import Tank
from tanks.shell import Shell
from tanks.land import Land
from machine import Pin,SPI
from test.fonts import vga2_8x8 as font1
from test.fonts import vga1_16x32 as font2
from tanks.my_input import Input
import time
import test.fonts.vga1_16x32 as font_text_big
from test.fonts import vga2_8x8 as font_text
my_input=Input()
st7789_res = 0
st7789_dc = 1
disp_width = 240
disp_height = 240
spi_sck=Pin(2)
spi_tx=Pin(3)
spi0=SPI(0,baudrate=62500000, phase=1, polarity=1, sck=spi_sck, mosi=spi_tx)
game='Train No.7'
choose_game=False
musicPlay=musicPlay()
welcome = welcome.ST7789(spi0, disp_width, disp_width,
reset=machine.Pin(st7789_res, machine.Pin.OUT),
dc=machine.Pin(st7789_dc, machine.Pin.OUT),
xstart=0, ystart=0, rotation=0)
display = st7789.ST7789(spi0,disp_width,disp_height,
dc=machine.Pin(st7789_dc, machine.Pin.OUT),
reset=machine.Pin(st7789_res, machine.Pin.OUT),
rotation=0)
welcome.text(font2, "Welcome!", 60, 50)
welcome.text(font2, "TIANK BETTLE", 30, 100,st7789.RED)
welcome.text(font2, "PRESS START", 30, 150)
welcome.text(font2, "TO START", 50, 190)
while not my_input.Start():
musicPlay.welPlay()
musicPlay.stop()
del(welcome)
time.sleep(1)
width = disp_width
height = disp_height
# display_buffer = bytearray(width * height) # 2-bytes per pixel (RGB565)
# display.init(display_buffer)
display.fill(st7789.BLACK)
# Colour constants
SKY_COLOR = st7789.color565(165, 182, 255)
GROUND_COLOR = st7789.color565(9,84,5)
# Different tank colors for player 1 and player 2
# These colors must be unique as well as the GROUND_COLOR
TANK_COLOR_P1 = st7789.color565(216, 216, 153)
TANK_COLOR_P2 = st7789.color565(219, 163, 82)
SHELL_COLOR = st7789.color565(255,255,255)
TEXT_COLOR = st7789.color565(255,255,255)
TEXT_COLOR_ACTIVE = st7789.YELLOW
CELE_COLOR = st7789.YELLOW
# States are:
# start - timed delay before start
# player1 - waiting for player to set position
# player1fire - player 1 fired
# player2 - player 2 set position
# player2fire - player 2 fired
# game_over_1 / game_over_2 - show who won 1 = player 1 won etc.
game_state = "start"
# switch button mode from angle to power
key_mode = "angle"
# Tank 1 = Left
tank1 = Tank(display, "left", TANK_COLOR_P1,SKY_COLOR)
# Tank 2 = Right
tank2 = Tank(display, "right", TANK_COLOR_P2,SKY_COLOR)
# Only fire one shell at a time, a single shell object can be used for both player 1 and player 2
shell = Shell(display, SHELL_COLOR,SKY_COLOR)
ground = Land(display, GROUND_COLOR)
WinPage=1
def mycallback(t):
musicPlay.bgmPlay()
# periodic with 100ms period
bgmTimer=Timer(period=100, mode=Timer.ONE_SHOT, callback=mycallback)
bgmTimer.init(period=200, mode=Timer.PERIODIC, callback=mycallback)
def run_game():
while True:
global key_mode, game_state,WinPage
## Draw methods
# display.set_pen(*SKY_COLOR)
# display.clear()
if game_state not in ['game_over_1','game_over_2']:
tank1.draw ()
tank2.draw ()
if game_state=='start':
display.fill(SKY_COLOR)
ground.draw()
game_state='player1'
print(game_state)
if (game_state == "player1fire" or game_state == "player2fire"):
shell.draw()
utime.sleep(0.1)
# print('shell')
# display.set_pen(*TEXT_COLOR)
if (game_state == "player1" or game_state == "player1fire"):
display.text(font_text,"Player 1", 10, 10,TEXT_COLOR)
if (key_mode == "power"):
# display.set_pen(*TEXT_COLOR_ACTIVE)
display.text(font_text,"Power:{:>3d}".format(tank1.get_gun_power())+"%", 10, 20,TEXT_COLOR_ACTIVE)#
else:
display.text(font_text,"Power:{:>3d}".format(tank1.get_gun_power())+"%", 10, 20,TEXT_COLOR)
if (key_mode == "angle"):
# display.set_pen(*TEXT_COLOR_ACTIVE)
display.text(font_text,"Angle:{:>3d}".format(tank1.get_gun_angle())+' ', 10, 30,TEXT_COLOR_ACTIVE)
else:
#lay.set_pen(*TEXT_COLOR)
display.text(font_text,"Angle:{:>3d}".format(tank1.get_gun_angle())+' ', 10, 30,TEXT_COLOR)
if (game_state == "player2" or game_state == "player2fire"):
display.text(font_text,"Player 2", 150, 10,TEXT_COLOR)
if (key_mode == "power"):
# display.set_pen(*TEXT_COLOR_ACTIVE)
display.text(font_text,"Power:{:>3d}".format(tank2.get_gun_power())+"%", 150, 20,TEXT_COLOR_ACTIVE)
else:
display.text(font_text,"Power:{:>3d}".format(tank2.get_gun_power())+"%", 150, 20,TEXT_COLOR)
if (key_mode == "angle"):
# display.set_pen(*TEXT_COLOR_ACTIVE)
display.text(font_text,"Angle:{:>3d}".format(tank2.get_gun_angle())+' ', 150, 30,TEXT_COLOR_ACTIVE)
else:
# display.set_pen(*TEXT_COLOR)
display.text(font_text,"Angle:{:>3d}".format(tank2.get_gun_angle())+' ', 150, 30,TEXT_COLOR)
if (game_state == "game_over_1" and WinPage):
display.fill(st7789.BLACK)
display.text(font_text_big,"Game Over", 50, 20,TEXT_COLOR)
display.text(font_text_big,"Player 1", 60, 80,CELE_COLOR)
display.text(font_text_big,"Wins!", 80, 110,CELE_COLOR)
display.text(font_text_big,"PRESS B", 60, 175,TEXT_COLOR)
display.text(font_text_big,"TO CONTINUE.", 30, 200,TEXT_COLOR)
WinPage=0
if (game_state == "game_over_2" and WinPage):
display.fill(st7789.BLACK)
display.text(font_text_big,"Game Over", 50, 20,TEXT_COLOR)
display.text(font_text_big,"Player 2", 60, 80,CELE_COLOR)
display.text(font_text_big,"Wins!", 80, 110,CELE_COLOR)
display.text(font_text_big,"PRESS B", 60, 175,TEXT_COLOR)
display.text(font_text_big,"TO CONTINUE.", 30, 200,TEXT_COLOR)
WinPage=0
# display.update()
## Update methods
# Only read keyboard in certain states
if (game_state == 'player1'):
player1_fired = player_keyboard("left")
print(player1_fired)
if (player1_fired == True):
# Set shell position to end of gun
# Use gun_positions so we can get start position
gun_positions = tank1.calc_gun_positions()
start_shell_pos = (gun_positions[3][0],gun_positions[3][1]+2)
shell.set_start_position(start_shell_pos)
shell.set_current_position(start_shell_pos)
global key_mode, game_state
game_state = 'player1fire'
musicPlay.shotSoundPlay()
print(game_state)
shell.set_angle(math.radians (tank1.get_gun_angle()))
shell.set_power(tank1.get_gun_power() / 40)
shell.set_time(0)
if (game_state == 'player1fire'):
shell.update_shell_position ("left")
# shell value is whether the shell is inflight, hit or missed
shell_value = detect_hit("left")
# shell_value 20 is if other tank hit
if (shell_value >= 20):
game_state = 'game_over_1'
# 10 is offscreen and 11 is hit ground, both indicate missed
elif (shell_value >= 10):
# reset key mode to angle
key_mode = "angle"
game_state = 'player2'
if (game_state == 'player2'):
player2_fired = player_keyboard("right")
if (player2_fired == True):
# Set shell position to end of gun
# Use gun_positions so we can get start position
gun_positions = tank2.calc_gun_positions ()
start_shell_pos = (gun_positions[3][0],gun_positions[3][1]+2)
shell.set_start_position(start_shell_pos)
shell.set_current_position(start_shell_pos)
game_state = 'player2fire'
musicPlay.shotSoundPlay()
shell.set_angle(math.radians (tank2.get_gun_angle()))
shell.set_power(tank2.get_gun_power() / 40)
shell.set_time(0)
if (game_state == 'player2fire'):
shell.update_shell_position ("right")
# shell value is whether the shell is inflight, hit or missed
shell_value = detect_hit("right")
# shell_value 20 is if other tank hit
if (shell_value >= 20):
game_state = 'game_over_2'
# 10 is offscreen and 11 is hit ground, both indicate missed
elif (shell_value >= 10):
game_state = 'player1'
# reset key mode to angle
key_mode = "angle"
if (game_state == 'game_over_1' or game_state == 'game_over_2'):
global bgmTimer
bgmTimer.deinit()
# Allow space key or left-shift (picade) to continue
if (my_input.B()) :
# Reset position of tanks and terrain
setup()
musicPlay.overPlay()
if my_input.Start():
setup()
if (my_input.Select()==1):
musicPlay.switch()
# Reset
def setup():
global game_state, key_mode,WinPage,bgmTimer
bgmTimer.init(period=200, mode=Timer.PERIODIC, callback=mycallback)
# reset key mode to angle
key_mode = "angle"
ground.setup()
# Get positions of tanks from ground generator
tank1.set_position(ground.get_tank1_position())
tank2.set_position(ground.get_tank2_position())
game_state = "start"
WinPage=1
# Detects if the shell has hit something.
# Simple detection looks at colour of the screen at the position
# uses an offset to not detect the actual shell
# Return 0 for in-flight,
# 1 for offscreen temp (too high),
# 10 for offscreen permanent (too far),
# 11 for hit ground,
# 20 for hit other tank
def detect_hit (left_right):
(shell_x, shell_y) = shell.get_current_position()
# Add offset (3 pixels)
# offset left/right depending upon direction of fire
if (left_right == "left"):
shell_x += 2
else:
shell_x -= 2
shell_y += 2
offset_position = (math.floor(shell_x), math.floor(shell_y))
# Check whether it's off the screen
# may be temporary if just y axis, permanent if x
if (shell_x > width or shell_x <= 0 or shell_y >= height):
shell.shell_hide()
return 10
if (shell_y < 1):
# special case if gone beyond size of screen then that's too far
if (shell_y < 0-height):
shell.shell_hide()
return 10
shell.shell_hide()
return 1
# Get colour at position
# color_values = get_display_bytes(*offset_position)
# color_values=color_to_bytes(GROUND_COLOR)
# ground_color_bytes = color_to_bytes(GROUND_COLOR)
# tank1_color_bytes = color_to_bytes(TANK_COLOR_P1)
# tank2_color_bytes = color_to_bytes(TANK_COLOR_P2)
shell_x=offset_position[0]
shell_y=offset_position[1]
tank1_pos=tank1.position
tank2_pos=tank2.position
land_pos=ground.get_land_height()
print(shell_x, shell_y,land_pos[shell_x])
print(tank1_pos)
print(tank2_pos)
if (shell_y >= land_pos[shell_x]):
# Hit ground
shell.shell_hide()
return 11
xscale=15
y_up_scale=0
y_down_scale=15
if (left_right == 'left' and tank2_pos[0]+3<=shell_x <= tank2_pos[0]+xscale and tank2_pos[1]-y_down_scale<=shell_y <= tank2_pos[1]+y_up_scale):
# Hit tank 2
musicPlay.shotedSoundPlay()
return 20
if (left_right == 'right' and tank1_pos[0]<=shell_x <= tank1_pos[0]+xscale-3 and tank1_pos[1]-y_down_scale<=shell_y <= tank1_pos[1]+y_up_scale):
# Hit tank 1
musicPlay.shotedSoundPlay()
return 20
return 0
# Handles keyboard for players
# Although named keyboard (consistancy with pygame zero version) - for the pico this refers to buttons
# If player has hit fire key (space) then returns True
# Otherwise changes angle of gun if applicable and returns False
def player_keyboard(left_right):
global key_mode
# change key_mode between angle and power using B button
# print('B:'+str(my_input.B()))
if (my_input.B()==1) :
if key_mode == "angle":
key_mode = "power"
else:
key_mode = "angle"
# add delay to prevent accidental double press
utime.sleep(0.01)
# A button is fire
# print('A:'+str(my_input.A()))
if (my_input.A()==1) :
return True
utime.sleep(0.1)
# Up moves firing angle upwards or increase power
adjustAngle=3
if (my_input.y()==-1) :
if (key_mode == "angle" and left_right == 'left'):
tank1.change_gun_angle(adjustAngle)
elif (key_mode == "angle" and left_right == 'right'):
tank2.change_gun_angle(adjustAngle)
elif (key_mode == "power" and left_right == 'left'):
tank1.change_gun_power(adjustAngle)
elif (key_mode == "power" and left_right == 'right'):
tank2.change_gun_power(adjustAngle)
# Down moves firing angle downwards or decrease power
if (my_input.y()==1) :
if (key_mode == "angle" and left_right == 'left'):
tank1.change_gun_angle(-adjustAngle)
elif (key_mode == "angle" and left_right == 'right'):
tank2.change_gun_angle(-adjustAngle)
elif (key_mode == "power" and left_right == 'left'):
tank1.change_gun_power(-adjustAngle)
elif (key_mode == "power" and left_right == 'right'):
tank2.change_gun_power(-adjustAngle)
return False
# Returns as list
# def get_display_bytes (x, y):
# buffer_pos = (x*2) + (y*width*2)
# byte_list = [display_buffer[buffer_pos], display_buffer[buffer_pos+1]]
# return (byte_list)
def color_to_bytes (color):
# r, g, b = color
# bytes = [0,0]
# bytes[0] = r & 0xF8
# bytes[0] += (g & 0xE0) >> 5
# bytes[1] = (g & 0x1C) << 3
# bytes[1] += (b & 0xF8) >> 3
return color
setup()
run_game()
land.py
import math
import random
# Creates the land for the tanks to go on.
# Also positions the tanks - which can be retrieved using get_tank_position method
# How big a chunk to split up x axis
LAND_CHUNK_SIZE = 20
# Max that land can go up or down within chunk size
LAND_MAX_CHG = 20
# Max height of ground
LAND_MIN_Y = 100
# Ssize of land for tank
LAND_TANK_SIZE = 20
class Land:
def __init__ (self, display, ground_color):
self.display = display
self.ground_color = ground_color
self.screen_size = (240, 240)
self.setup()
def setup(self):
# Create an array of land y values - gravity means that all blocks below are solid (no caves)
# Initially all set to 0
self.land_y_positions = [0] *240
# Setup landscape (these positions represent left side of platform)
# Choose a random position (temp values - to be stored in tank object)
# The complete x,y co-ordinates will be saved in a tuple in left_tank_rect and right_tank_rect
# includes a DMZ of 40 pixels
left_tank_x_position = random.randint (10,int(self.screen_size[0]/2)-30)
right_tank_x_position = random.randint (int(self.screen_size[0]/2)+30,self.screen_size[0]-40)
self.tank1_position = (left_tank_x_position,0)
self.tank2_position = (right_tank_x_position,0)
# Sub divide screen into chunks for the landscape
current_land_x = 0
next_land_x = 0 + LAND_CHUNK_SIZE
# start y position at least 50 from top 20 from bottom
current_land_y = random.randint (50,240-20)
self.land_y_positions[current_land_x] = current_land_y
while (current_land_x < self.screen_size[0]):
# If where tank is then we create a flat area for tank to sit on
if (current_land_x == left_tank_x_position):
# handle tank platform
self.tank1_position = (current_land_x, int(current_land_y))
# Add another 60 pixels further along at same y position (level ground for tank to sit on)
for i in range (0, LAND_TANK_SIZE):
self.land_y_positions[current_land_x] = int(current_land_y)
current_land_x += 1
continue
elif (current_land_x == right_tank_x_position):
# handle tank platform
self.tank2_position = (current_land_x, int(current_land_y))
# Add another 60 pixels further along at same y position (level ground for tank to sit on)
for i in range (0, LAND_TANK_SIZE):
self.land_y_positions[current_land_x] = int(current_land_y)
current_land_x += 1
continue
# Checks to see if next position will be where the tanks are
if (current_land_x < left_tank_x_position and current_land_x + LAND_CHUNK_SIZE >= left_tank_x_position):
# set x position to tank position
next_land_x = left_tank_x_position
elif (current_land_x < right_tank_x_position and current_land_x + LAND_CHUNK_SIZE >= right_tank_x_position):
# set x position to tank position
next_land_x = right_tank_x_position
elif (current_land_x + LAND_CHUNK_SIZE > self.screen_size[0]):
next_land_x = self.screen_size[0]
else:
next_land_x = current_land_x + LAND_CHUNK_SIZE
# Set the y height
next_land_y = current_land_y + random.randint(0-LAND_MAX_CHG,LAND_MAX_CHG)
# check not too high or too lower (note the reverse logic as high y is bottom of screen)
if (next_land_y > self.screen_size[1]): # Bottom of screen
next_land_y = self.screen_size[1]
if (next_land_y < LAND_MIN_Y):
next_land_y = LAND_MIN_Y
# Add to list
# Work through until current_land_x = next_land_x
# delta is how much the y value changes per increment
# Check not flat first
if (next_land_y == current_land_y or next_land_x == current_land_x):
y_delta = 0
else:
y_delta = (next_land_y - current_land_y) / (next_land_x - current_land_x)
for i in range (current_land_x, next_land_x):
current_land_y += y_delta
self.land_y_positions[current_land_x] = int(current_land_y)
current_land_x += 1
def get_tank1_position(self):
return self.tank1_position
def get_tank2_position(self):
return self.tank2_position
def get_land_height(self):
return self.land_y_positions
def draw (self):
# self.display.set_pen(*self.ground_color)
current_land_x = 0
for this_pos in self.land_y_positions:
for this_y in range (self.land_y_positions[current_land_x], self.screen_size[1]):
self.display.pixel(current_land_x, int(this_y),self.ground_color)
current_land_x += 1
myinput.py
from machine import ADC, Pin
class Input():
def __init__(self):
self.xAxis = ADC(Pin(28))
self.yAxis = ADC(Pin(29))
self.buttonB = Pin(5,Pin.IN, Pin.PULL_UP) #B
self.buttonA = Pin(6,Pin.IN, Pin.PULL_UP) #A
self.buttonStart = Pin(7,Pin.IN, Pin.PULL_UP) #A
self.buttonSelect = Pin(8,Pin.IN, Pin.PULL_UP) #A
def A(self):
if self.buttonA.value() == 0:
return True
else:
return False
def B(self):
if self.buttonB.value() == 0:
return True
else:
return False
def Start(self):
if self.buttonStart.value() == 0:
return True
else:
return False
def Select(self):
if self.buttonSelect.value() == 0:
return True
else:
return False
def x(self, down=20000, up=50000):
_temp = self.yAxis.read_u16()
if(_temp<down):
return -1
elif(_temp>up):
return 1
else:
return 0
def y(self, down=20000, up=50000):
_temp = self.xAxis.read_u16()
if(_temp<down):
return -1
elif(_temp>up):
return 1
else:
return 0
def x_raw(self):
return self.yAxis.read_u16()
def y_raw(self):
return self.xAxis.read_u16()
def test(self):
import time
while True:
print(f"x:{self.x()} y:{self.y()} x_raw:{self.x_raw()} y_raw:{self.y_raw()} A:{self.A()} B:{self.B()} Start:{self.Start()} Select:{self.Select()}")
if self.Start() and self.Select():
return
time.sleep_ms(50)
shell.py
import math
class Shell:
def __init__ (self, display, shell_color,background_color):
self.display = display
self.shell_color = shell_color
self.background_color = background_color
self.start_position = (0,0)
self.current_position = (0,0)
self.power = 1
self.angle = 0
self.time = 0
self.post_position=self.current_position
self.shell_size=2
def set_start_position(self, position):
self.start_position = position
def get_start_position(self):
return self.start_position
def set_current_position(self, position):
self.current_position = position
def get_current_position(self):
return self.current_position
def set_angle(self, angle):
self.angle = angle
def set_power(self, power):
self.power = power
def set_time(self, time):
self.time = time
def draw (self):
(xpos, ypos) = self.current_position
(postXpos,postYpos)=self.post_position
# Create rectangle of the shell
# self.display.set_pen(*self.shell_color)
self.display.rect(int(postXpos),int(postYpos),self.shell_size,self.shell_size,self.background_color)
self.display.rect(int(xpos), int(ypos), self.shell_size, self.shell_size,self.shell_color)
self.post_position=self.current_position
def shell_hide(self):
(postXpos,postYpos)=self.post_position
self.display.rect(int(postXpos),int(postYpos),self.shell_size,self.shell_size,self.background_color)
def update_shell_position (self, left_right):
init_velocity_y = self.power * math.sin(self.angle)
# Direction - multiply by -1 for left to right
if (left_right == 'left'):
init_velocity_x = self.power * math.cos(self.angle)
else:
init_velocity_x = self.power * math.cos(math.pi - self.angle)
# Gravity constant is 9.8 m/s^2 but this is in terms of screen so instead use a sensible constant (0.004)
GRAVITY_CONSTANT = 0.008
# Constant to give a sensible distance on x axis (1.5 on pygame zero)
DISTANCE_CONSTANT = 1.5
# Wind is not included in this version, to implement then decreasing wind value is when the wind is against the fire direction
# wind > 1 is where wind is against the direction of fire. Wind must never be 0 or negative (which would make it impossible to fire forwards)
wind_value = 1
# time is calculated in update cycles
shell_x = self.start_position[0] + init_velocity_x * self.time * DISTANCE_CONSTANT
shell_y = self.start_position[1] + -1 * ((init_velocity_y * self.time) - (0.5 * GRAVITY_CONSTANT * self.time * self.time * wind_value))
self.current_position = (shell_x, shell_y)
self.time += 4
tank.py
import math
TANK_COLOR_P1 = (216, 216, 153)
TANK_COLOR_P2 = (219, 163, 82)
SHELL_COLOR = (255,255,255)
# Add constants for gun size
GUN_LENGTH = 10
GUN_DIAMETER = 2
class Tank:
def __init__(self, display, left_right, tank_color,background_color):
self.display = display
self.left_right = left_right
self.tank_color = tank_color
self.background_color=background_color
self.position = (0,0)
# Angle that the gun is pointing (degrees relative to horizontal)
if (left_right == "left"):
self.gun_angle = 10
else :
self.gun_angle = 50
# Amount of power to fire with - is divided by 40 to give scale 10 to 100
self.gun_power = 40
self.post_gun_position=self.calc_gun_positions()
def set_position (self, position):
self.position = position
def get_position (self):
return self.position
def set_gun_angle (self, angle):
self.gun_angle = angle
def change_gun_angle (self, amount):
self.gun_angle += amount
if self.gun_angle > 85:
self.gun_angle = 85
if self.gun_angle < -20:
self.gun_angle = -20
def get_gun_angle (self):
return self.gun_angle
def set_gun_power (self, power):
self.gun_power = power
def change_gun_power (self, amount):
self.gun_power += amount
if self.gun_power > 100:
self.gun_power = 100
if self.gun_power < 10:
self.gun_power = 10
def get_gun_power (self):
return self.gun_power
# Draws tank (including gun - which depends upon direction and aim)
# self.left_right can be "left" or "right" to depict which position the tank is in
# tank_start_pos requires x, y co-ordinates as a tuple
# angle is relative to horizontal - in degrees
def draw (self):
# self.display.set_pen(*self.tank_color)
# Tracks
self.display.hline(self.position[0]+6, self.position[1]-10, 20,self.tank_color)
self.display.hline(self.position[0]+5, self.position[1]-9, 22,self.tank_color)
self.display.hline(self.position[0]+4, self.position[1]-8, 24,self.tank_color)
self.display.hline(self.position[0]+3, self.position[1]-7, 26,self.tank_color)
self.display.hline(self.position[0]+2, self.position[1]-6, 28,self.tank_color)
self.display.hline(self.position[0]+1, self.position[1]-5, 30,self.tank_color)
self.display.hline(self.position[0]+2, self.position[1]-4, 28,self.tank_color)
self.display.hline(self.position[0]+3, self.position[1]-3, 26,self.tank_color)
self.display.hline(self.position[0]+4, self.position[1]-2, 24,self.tank_color)
self.display.hline(self.position[0]+5, self.position[1]-1, 22,self.tank_color)
self.display.hline(self.position[0]+6, self.position[1], 20,self.tank_color)
# Hull
self.display.rect(self.position[0]+7, self.position[1]-13, 18, 3,self.tank_color)
self.display.hline(self.position[0]+8, self.position[1]-14, 16,self.tank_color)
self.display.hline(self.position[0]+6, self.position[1]-15, 20,self.tank_color)
self.display.hline(self.position[0]+8, self.position[1]-16, 16,self.tank_color)
# Gun position involves more complex calculations so in a separate function
self.erase_gun(self.post_gun_position)
self.draw_gun(self.calc_gun_positions())
self.post_gun_position=self.calc_gun_positions()
# Draw gun on tank - this is fairly crude designed for display with limited pixels
# Draws as thick line instead of parallelogram which is used on higher resolution screens
def erase_gun(self, gun_positions):
# Just used co-ords 0 and 1 and draw line radius pixels thick
if (self.left_right == "left"):
# split into variables to made code easier to follow
start_x = gun_positions[1][0]
start_y = gun_positions[1][1]
end_x = gun_positions[2][0]
end_y = gun_positions[2][1]
else :
# split into variables to made code easier to follow
start_x = gun_positions[2][0]
start_y = gun_positions[2][1]
end_x = gun_positions[1][0]
end_y = gun_positions[1][1]
# y delta is amount of change in y between first and last x position
# if flat then set to 0 (avoids divide by zero)
if (end_y == start_y or end_x == start_x):
y_delta = 0
else:
y_delta = (end_y - start_y) / (end_x - start_x)
current_x = int(start_x)
current_y = int(start_y)
for x in range (start_x, end_x):
for y_offset in range (0, GUN_DIAMETER):
#print ("Drawing gun {} {}".format(current_x, int(current_y+y_offset)))
self.display.pixel(current_x, int(current_y+y_offset),self.background_color)
current_x += 1
current_y += y_delta
def draw_gun(self, gun_positions):
# Just used co-ords 0 and 1 and draw line radius pixels thick
if (self.left_right == "left"):
# split into variables to made code easier to follow
start_x = gun_positions[1][0]
start_y = gun_positions[1][1]
end_x = gun_positions[2][0]
end_y = gun_positions[2][1]
else :
# split into variables to made code easier to follow
start_x = gun_positions[2][0]
start_y = gun_positions[2][1]
end_x = gun_positions[1][0]
end_y = gun_positions[1][1]
# y delta is amount of change in y between first and last x position
# if flat then set to 0 (avoids divide by zero)
if (end_y == start_y or end_x == start_x):
y_delta = 0
else:
y_delta = (end_y - start_y) / (end_x - start_x)
current_x = int(start_x)
current_y = int(start_y)
for x in range (start_x, end_x):
for y_offset in range (0, GUN_DIAMETER):
#print ("Drawing gun {} {}".format(current_x, int(current_y+y_offset)))
self.display.pixel(current_x, int(current_y+y_offset),self.tank_color)
current_x += 1
current_y += y_delta
# Calculate the polygon positions for the gun barrel
# This calculates a polygon based on the pygame zero method
# For the Pico version use point 0 and 1 for drawing line for barrel
def calc_gun_positions (self):
(xpos, ypos) = self.position
# Set the start of the gun (top of barrel at point it joins the tank)
if (self.left_right == "right"):
gun_start_pos_top = (xpos+6, ypos-15)
else:
gun_start_pos_top = (xpos+26, ypos-15)
# Convert angle to radians (for right subtract from 180 deg first)
relative_angle = self.gun_angle
if (self.left_right == "right"):
relative_angle = 180 - self.gun_angle
angle_rads = relative_angle * (math.pi / 180)
# Create vector based on the direction of the barrel
# Y direction *-1 (due to reverse y of screen)
gun_vector = (math.cos(angle_rads), math.sin(angle_rads) * -1)
# Determine position bottom of barrel
# Create temporary vector 90deg to existing vector
if (self.left_right == "right"):
temp_angle_rads = math.radians(relative_angle - 90)
else:
temp_angle_rads = math.radians(relative_angle + 90)
temp_vector = (math.cos(temp_angle_rads), math.sin(temp_angle_rads) * -1)
gun_start_pos_bottom = (gun_start_pos_top[0] + temp_vector[0] * GUN_DIAMETER, gun_start_pos_top[1] + temp_vector[1] * GUN_DIAMETER)
# Calculate barrel positions based on vector from start position
gun_positions = [
gun_start_pos_bottom,
gun_start_pos_top,
(gun_start_pos_top[0] + gun_vector[0] * GUN_LENGTH, gun_start_pos_top[1] + gun_vector[1] * GUN_LENGTH),
(gun_start_pos_bottom[0] + gun_vector[0] * GUN_LENGTH, gun_start_pos_bottom[1] + gun_vector[1] * GUN_LENGTH),
]
return gun_positions
musicplay.py
from buzzer_music import music
from time import sleep
#Example songs
from machine import Pin
class musicPlay:
def __init__(self):
self.play=1
self.welcomeMusic="0 A#6 1 43;1 C#7 1 43;2 B6 1 43;3 D7 1 43;4 A#6 1 43;5 C#7 1 43;6 B6 1 43;7 G6 1 43;8 B6 1 43;9 C#7 1 43;10 A6 1 43;11 D7 1 43"
self.backGroundMusic="0 C5 1 43;1 A#4 1 43;3 G4 1 43;4 C5 1 43;7 G4 1 43;8 C5 1 43;10 A#4 1 43;11 G4 1 43;12 A4 1 43;13 B4 1 43;14 A#4 1 43;15 G#4 1 43;15 G4 1 43;6 A4 1 43"
self.shotedSound="0 D3 1 43"
self.shotSound="0 C3 1 43"
self.overMusic="0 D#5 1 43;0 C#5 1 43;2 D#5 1 43;2 C#5 1 43;1 F5 1 43;3 D5 1 43;4 D#5 1 43;4 C#5 1 43;6 D#5 1 43;6 C#5 1 43;5 A#4 1 43;7 E5 1 43;8 C#5 1 43;8 D#5 1 43;9 A5 1 43;10 C#5 1 43;10 D#5 1 43;11 F#5 1 43"
self.welcomeMusicPlay=music(self.welcomeMusic,pins=[Pin(23)])
self.backGroundMusicPlay = music(self.backGroundMusic, pins=[Pin(23)])
self.overMusicPlay = music (self.overMusic,pins=[Pin(23)])
def bgmPlay(self):
if self.play:
self.backGroundMusicPlay.tick()
def welPlay(self):
if self.play:
self.welcomeMusicPlay.tick()
sleep(0.1)
def overPlay(self):
if self.play:
self.overMusicPlay.tick()
sleep(0.1)
def shotSoundPlay(self):
if self.play:
sound=music(self.shotSound,pins=[Pin(23)])
sound.tick()
sleep(0.1)
sound.stop()
def shotedSoundPlay(self):
if self.play:
sound=music(self.shotedSound,pins=[Pin(23)])
sound.tick()
sleep(0.1)
sound.stop()
def stop(self):
self.welcomeMusicPlay.stop()
def switch(self):
if self.play:
self.play=0
else:
self.play=1
self.stop()
Demos
Last updated
Was this helpful?