|
1 | | -secrets here |
| 1 | +# Adventure 8: ballgame.py |
| 2 | +# |
| 3 | +# From the book: "Adventures in Minecraft", 2nd Edition |
| 4 | +# written by David Whale and Martin O'Hanlon, Wiley, 2017 |
| 5 | +# http://eu.wiley.com/WileyCDA/WileyTitle/productCd-1119439582.html |
| 6 | +# |
| 7 | +# This program is a complete mini-game involving rolling a ball on a table |
| 8 | + |
| 9 | +# Import necessary modules |
| 10 | +import microbit |
| 11 | +import mcpi.minecraft as minecraft |
| 12 | +import mcpi.block as block |
| 13 | +import time |
| 14 | +import random |
| 15 | + |
| 16 | +# CONSTANTS |
| 17 | +# Change these to vary the size/difficulty of the game |
| 18 | +TABLE_WIDTH = 20 |
| 19 | +TABLE_DEPTH = 20 |
| 20 | +TREASURE_COUNT = 25 |
| 21 | +TABLE = block.STONE.id |
| 22 | +BALL = block.CACTUS.id |
| 23 | +TREASURE = block.GOLD_BLOCK.id |
| 24 | + |
| 25 | +# Connect to the Minecraft game and |
| 26 | +# let the player know that the micro:bit is connected |
| 27 | +mc = minecraft.Minecraft.create() |
| 28 | +mc.postToChat("BBC micro:bit joined the game") |
| 29 | + |
| 30 | +# VARIABLES |
| 31 | + |
| 32 | +table_x = None |
| 33 | +table_y = None |
| 34 | +table_z = None |
| 35 | +ball_x = None |
| 36 | +ball_y = None |
| 37 | +ball_z = None |
| 38 | +speed_x = 0 |
| 39 | +speed_z = 0 |
| 40 | +remaining = 1 |
| 41 | + |
| 42 | +# Build a complete game table |
| 43 | +def build_table(x, y, z): |
| 44 | + global table_x, table_y, table_z |
| 45 | + # Put a small gap around the edge in case of nearby mountains |
| 46 | + GAP = 10 |
| 47 | + mc.setBlocks(x-GAP, y, z-GAP, x+TABLE_WIDTH+GAP, y+GAP, z+TABLE_DEPTH+GAP, |
| 48 | + block.AIR.id) |
| 49 | + # Build the table as a big block |
| 50 | + mc.setBlocks(x-1, y, z-1, x+TABLE_WIDTH+1, y+1, z+TABLE_DEPTH+1, TABLE) |
| 51 | + # Carve out the middle, this creates the wall around the table |
| 52 | + mc.setBlocks(x, y+1, z, x+TABLE_WIDTH, y+1, z+TABLE_DEPTH, block.AIR.id) |
| 53 | + # Remember where the table was built, for later |
| 54 | + table_x = x |
| 55 | + table_y = y |
| 56 | + table_z = z |
| 57 | + |
| 58 | +# Place random treasure blocks on the table |
| 59 | +def place_treasure(): |
| 60 | + y = table_y |
| 61 | + for i in range(TREASURE_COUNT): |
| 62 | + # BUGFIX: Prevent block being re-created in same pos |
| 63 | + # which would make it impossible to complete the game |
| 64 | + while True: |
| 65 | + # Choose a random position for the next block |
| 66 | + x = random.randint(0, TABLE_WIDTH) + table_x |
| 67 | + z = random.randint(0, TABLE_DEPTH) + table_z |
| 68 | + # Is it already a treasure block? |
| 69 | + b = mc.getBlock(x,y,z) |
| 70 | + if b == TREASURE: continue # if so, loop round to try again |
| 71 | + # Place treasure at the new random location |
| 72 | + mc.setBlock(x, y, z, TREASURE) |
| 73 | + break # break out of loop to move to next treasure block |
| 74 | + # BUGFIX END |
| 75 | + |
| 76 | +# Move the ball based on the present ball speed |
| 77 | +def move_ball(): |
| 78 | + new_x = ball_x - speed_x |
| 79 | + new_z = ball_z - speed_z |
| 80 | + if ball_x != new_x or ball_z != new_z: # prevent screen flicker |
| 81 | + # Don't draw the ball if it falls off the table for some reason |
| 82 | + if is_on_table(new_x, new_z): |
| 83 | + move_ball_to(new_x, ball_y, new_z) |
| 84 | + |
| 85 | +# Move the ball to a specific position on the table |
| 86 | +def move_ball_to(x, y, z): |
| 87 | + global ball_x, ball_y, ball_z |
| 88 | + # Clear the existing ball if it has already been built |
| 89 | + if ball_x is not None: |
| 90 | + mc.setBlock(ball_x, ball_y, ball_z, block.AIR.id) |
| 91 | + # Update our record of where the ball is now |
| 92 | + ball_x = x |
| 93 | + ball_y = y |
| 94 | + ball_z = z |
| 95 | + # Build a block where the ball needs to be |
| 96 | + mc.setBlock(ball_x, ball_y, ball_z, BALL) |
| 97 | + |
| 98 | +# Calculate a new speed value, given the existing speed and amount of tilt. |
| 99 | +# You can change this algorithm to make the acceleration and deceleration of |
| 100 | +# the ball more life-like |
| 101 | +def new_speed(speed, tilt): |
| 102 | + # Create a dead spot when the micro:bit is flat to slow/stop the ball |
| 103 | + if abs(tilt) < 300: tilt = 0 |
| 104 | + # Reduce the range of the tilt to roughly -3..+3 |
| 105 | + tilt = tilt / 300 |
| 106 | + # Adjust the speed gradually each time, to simulate simple acceleration |
| 107 | + if tilt < speed: |
| 108 | + speed = speed - 1 |
| 109 | + elif tilt > speed: |
| 110 | + speed = speed + 1 |
| 111 | + return speed |
| 112 | + |
| 113 | +# Sense the amount of tilt of the micro:bit and update the speed variables |
| 114 | +def check_tilt(): |
| 115 | + global speed_x, speed_z |
| 116 | + speed_x = new_speed(speed_x, microbit.accelerometer.get_x()) |
| 117 | + speed_z = new_speed(speed_z, microbit.accelerometer.get_y()) |
| 118 | + #print(speed_x, speed_z) |
| 119 | + |
| 120 | +# Work out if a given ball position is on the table or not. |
| 121 | +# This can be used to prevent the ball rolling off the table. |
| 122 | +# It can also be used to sense if it rolls off the table. |
| 123 | +def is_on_table(x, z): |
| 124 | + if x < table_x or x > table_x + TABLE_WIDTH: |
| 125 | + return False |
| 126 | + if z < table_z or z > table_z + TABLE_DEPTH: |
| 127 | + return False |
| 128 | + return True |
| 129 | + |
| 130 | +# Check what is below the ball, and action it accordingly |
| 131 | +def check_below(): |
| 132 | + global remaining |
| 133 | + # What is below the ball? |
| 134 | + block_below = mc.getBlock(ball_x, ball_y-1, ball_z) |
| 135 | + if block_below == TREASURE: |
| 136 | + # It's treasure, so collect it and update the count of remaining items |
| 137 | + mc.setBlock(ball_x, ball_y-1, ball_z, block.AIR.id) |
| 138 | + remaining = remaining - 1 |
| 139 | + microbit.display.show(remaining) |
| 140 | + elif block_below == block.AIR.id: |
| 141 | + # It's a hole, so introduce a time-penalty |
| 142 | + move_ball_to(ball_x, ball_y-1, ball_z) |
| 143 | + while not microbit.button_b.was_pressed(): |
| 144 | + time.sleep(0.1) |
| 145 | + # Bounce the ball out to a random new position |
| 146 | + move_ball_to(table_x + random.randint(0, TABLE_WIDTH), table_y+1, |
| 147 | + table_z + random.randint(0, TABLE_DEPTH)) |
| 148 | + |
| 149 | +# Wait for the user to start the game |
| 150 | +def wait_for_start(): |
| 151 | + # A helpful message so that the user knows what to do |
| 152 | + mc.postToChat("press B to start") |
| 153 | + microbit.display.show("?") |
| 154 | + # Wait for the B button to be pressed on the micro:bit |
| 155 | + while not microbit.button_b.was_pressed(): |
| 156 | + time.sleep(0.1) |
| 157 | + |
| 158 | + # Countdown 5,4,3,2,1 |
| 159 | + for t in range(6): |
| 160 | + microbit.display.show(str(5-t)) |
| 161 | + time.sleep(0.5) |
| 162 | + |
| 163 | +# Build the complete game with table and treasure |
| 164 | +def build_game(): |
| 165 | + global speed_x, speed_z, remaining |
| 166 | + |
| 167 | + # Get Steve's position, so the table can be built nearby |
| 168 | + pos = mc.player.getTilePos() |
| 169 | + # Build the table close by |
| 170 | + build_table(pos.x, pos.y-2, pos.z) |
| 171 | + # Start the ball at a random position |
| 172 | + move_ball_to(table_x + random.randint(0, TABLE_WIDTH), table_y+1, |
| 173 | + table_z + random.randint(0, TABLE_DEPTH)) |
| 174 | + # Place all the treasure at random locations |
| 175 | + place_treasure() |
| 176 | + # Place Steve at a good viewing point |
| 177 | + mc.player.setTilePos(table_x + TABLE_WIDTH/2, table_y+1, table_z) |
| 178 | + # Start off the game variables at sensible values |
| 179 | + remaining = TREASURE_COUNT |
| 180 | + speed_x = 0 |
| 181 | + speed_z = 0 |
| 182 | + |
| 183 | +# Play one complete game from start to end |
| 184 | +def play_game(): |
| 185 | + # Remember what time the game was started |
| 186 | + start_time = time.time() |
| 187 | + # Loop around a game loop until all treasure is collected |
| 188 | + while remaining > 0: |
| 189 | + # Slow things down a bit, this loop runs 10 times per second |
| 190 | + time.sleep(0.1) |
| 191 | + # Find out how much the user is tilting the micro:bit |
| 192 | + check_tilt() |
| 193 | + # Move the ball based on the tilt |
| 194 | + move_ball() |
| 195 | + # Process treasure or air blocks below the ball |
| 196 | + check_below() |
| 197 | + # Remember what time the game finished |
| 198 | + end_time = time.time() |
| 199 | + # Display the total game time taken to collect all treasure |
| 200 | + mc.postToChat("game time=" + str(int(end_time-start_time))) |
| 201 | + |
| 202 | +# The main program. |
| 203 | +# Loops forever, until you press CTRL-C |
| 204 | +while True: |
| 205 | + wait_for_start() |
| 206 | + build_game() |
| 207 | + play_game() |
| 208 | + |
| 209 | +# END |
0 commit comments