Toggle Navigation
Hatchery
Eggs
CityControl
game.py
Users
Badges
Login
Register
game.py
raw
Content
import display import main import utils import random import custom_shapes import time import buttons import easydraw STATUS_BAR_HEIGHT = 40 PROGRESS_BAR_HEIGHT = 10 TEXT_BLOCK_HEIGHT = 55 CONTROL_TYPES = ["VERTICAL_SLIDER", "ROTARY_SWITCH", "BINARY_SWITCH"] INTRO_STATE = { "totalTime": 5, "remainingTime": 5, "assignment": "Starting the game in 5 seconds", } INTRO_HEALTH_INFO = { "death_limit": 0.0, "health": 50.0 } class GameState: def __init__(self, grid): self.state = INTRO_STATE self.health_info = INTRO_HEALTH_INFO self.grid = grid self.control_mapping = {} seen_switch = False seen_joystick = False self.intro = True self.first_command_received = False for i, control in enumerate(grid): control_type = control["type"] value = {"id": i, "name": control["name"], "type": control_type} if control_type == CONTROL_TYPES[2]: if seen_switch: self.control_mapping[buttons.BTN_A] = value else: seen_switch = True self.control_mapping[buttons.BTN_B] = value elif control_type in CONTROL_TYPES[:2]: if seen_joystick: self.control_mapping[buttons.BTN_LEFT] = value self.control_mapping[buttons.BTN_RIGHT] = value else: seen_joystick = True self.control_mapping[buttons.BTN_UP] = value self.control_mapping[buttons.BTN_DOWN] = value print("control mapping:", self.control_mapping) def update_grid(self, grid): self.grid = grid self.draw_game_screen() def update_assignment(self, assignment): if self.first_command_received: self.intro = False else: self.first_command_received = True self.state["assignment"] = assignment["text"] self.state["totalTime"] = assignment["time"] self.state["remainingTime"] = int(assignment["time"]) self.trigger_counter() def update_health_info(self, health_info): self.health_info = health_info self.draw_game_screen() def handle_button_press(self, button): if not self.intro and self.control_mapping: command = self.control_mapping[button] index = command["id"] if button == buttons.BTN_A or button == buttons.BTN_B: self.toggle_button(index) else: self.change_control_value(index, button == buttons.BTN_DOWN or button == buttons.BTN_RIGHT) self.draw_game_screen() return {"name": command["name"], "value": self.grid[index]["value"]} else: print("Ignoring button press, game not started yet") def toggle_button(self, index): self.grid[index]["value"] = not self.grid[index]["value"] def change_control_value(self, index, higher): control = self.grid[index] if higher: self.grid[index]["value"] = min(control["value"] + 1, control["max"]) else: self.grid[index]["value"] = max(control["value"] - 1, control["min"]) def draw_game_screen(self): display.drawFill(utils.BG_DARK) draw_status_bar(self.state["remainingTime"], self.state["assignment"]) draw_health_bar(self.health_info["death_limit"], self.health_info["health"]) def get_intro_name(control_type, seen_switch, seen_joystick): if control_type == CONTROL_TYPES[2]: return "Toggle with A" if seen_switch else "Toggle with B" elif control_type in CONTROL_TYPES[:2]: return "Use Joystick Right/Left" if seen_joystick else "Use Joystick Up/Down" else: raise Exception("Unknown control_type '%s' for get_intro_name(%s, %s, %s)" % (control_type, control_type, seen_switch, seen_joystick)) seen_switch = False seen_joystick = False for i, control in enumerate(self.grid): draw_control(control, i, get_intro_name(control["type"], seen_switch, seen_joystick) if self.intro else control["name"]) if control["type"] == CONTROL_TYPES[2]: seen_switch = True if control["type"] in CONTROL_TYPES[:2]: seen_joystick = True display.flush() def trigger_counter(self): if self.first_command_received: self.state["remainingTime"] = max(self.state["remainingTime"] - 1, 0) self.draw_game_screen() def text_centered(x, y, width, text, font, color): if font: text_width = display.getTextWidth(text, font) else: text_width = display.getTextWidth(text) pos_x = int((width - text_width) / 2) if font: display.drawText(x + pos_x, y, text, color, font) else: display.drawText(pos_x, y, text, color) def draw_assignment(assignment, x, y, width, height): ASSIGNMENT_FONT = utils.ARCADE_FONT[12] HORIZONTAL_PADDING = 10 LINE_SPACING = 4 split_assignment = assignment.upper() assignment_lines = easydraw.lineSplit( split_assignment, width=width, font=ASSIGNMENT_FONT ) line_heights = sum( [display.getTextHeight(line, ASSIGNMENT_FONT) for line in assignment_lines] ) total_height = line_heights + (len(assignment_lines) - 1) * LINE_SPACING for i, line in enumerate(assignment_lines): text_centered( x + HORIZONTAL_PADDING, y + (height - total_height) // 2 + (i * (line_heights // len(assignment_lines) + LINE_SPACING)), width - 2 * HORIZONTAL_PADDING, line, ASSIGNMENT_FONT, utils.BG_LIGHT, ) # Save some bucks on font licensing # Draw horizontal lines through assignment text for i in range(0, height, 2): display.drawLine(x, i, x + width, i, utils.BG_DARK) def draw_status_bar(remaining_time, assignment): TIME_REMAINING_WIDTH = WIFI_INDICATOR_WIDTH = 40 ASSIGNMENT_WIDTH = ( utils.DISPLAY_SIZE[0] - TIME_REMAINING_WIDTH - WIFI_INDICATOR_WIDTH ) # Background display.drawRect( 0, 0, utils.DISPLAY_SIZE[0], STATUS_BAR_HEIGHT, True, utils.BG_DARK ) # Time Remaining timer_height = display.getTextHeight(str(remaining_time), utils.ARCADE_FONT[9]) text_centered(0, STATUS_BAR_HEIGHT - ((STATUS_BAR_HEIGHT + timer_height) // 2), TIME_REMAINING_WIDTH, str(remaining_time), utils.ARCADE_FONT[9], utils.TEXT_MAIN) # Assignment draw_assignment( assignment, TIME_REMAINING_WIDTH, 0, ASSIGNMENT_WIDTH, STATUS_BAR_HEIGHT ) custom_shapes.draw_wifi_indicator( utils.DISPLAY_SIZE[0] - WIFI_INDICATOR_WIDTH, 5, utils.COLOR_SECONDARY, utils.BG_DARK, ) def draw_health_bar(death_limit, health): progress = health / 100.0 display.drawRect( 0, STATUS_BAR_HEIGHT, utils.DISPLAY_SIZE[0], PROGRESS_BAR_HEIGHT, True, utils.COLOR_SECONDARY, ) arrow_data = display.pngInfo(utils.PROGRESS_ARROW_RED) ARROW_SPACING = 3.0 death_limit_width = round((death_limit / 100.0) * utils.DISPLAY_SIZE[0]) arrow_amount = 1 + int(death_limit_width / (arrow_data[0] + ARROW_SPACING)) display.drawRect( 0, STATUS_BAR_HEIGHT, death_limit_width, PROGRESS_BAR_HEIGHT, True, utils.PROGRESS_GREEN, ) for i in range(arrow_amount): current_x = int(i * (arrow_data[0] + ARROW_SPACING)) if current_x >= 0: display.drawPng( current_x, STATUS_BAR_HEIGHT, utils.PROGRESS_ARROW_RED, ) health_bar_width = round(progress * utils.DISPLAY_SIZE[0]) if health_bar_width > 0: display.drawRect( death_limit_width, STATUS_BAR_HEIGHT, health_bar_width - death_limit_width, PROGRESS_BAR_HEIGHT, True, utils.PROGRESS_GREEN, ) def draw_control_text(text, x, y, width, height, is_light): TEXT_VERTICAL_SPACING = 3 FONT = utils.ARCADE_FONT[6] lines = [t.upper() for t in text.split(" ")] line_heights = [display.getTextHeight(line, FONT) for line in lines] total_height = sum(line_heights, (len(lines) - 1) * TEXT_VERTICAL_SPACING) if total_height > height: raise Exception( f"Cannot render text '{text}' using given height constraint {height}px, need {total_height}px" ) base_y = y + (height - total_height) // 2 for i, line in enumerate(lines): text_centered( x, base_y + sum(line_heights[0:i]) + i * TEXT_VERTICAL_SPACING, width, line, FONT, utils.BG_DARK if is_light else utils.BG_LIGHT, ) def draw_binary_control_indicator(text, enabled, x, y, width, is_light): INDICATOR_HEIGHT = 20 BORDER_WIDTH = 2 # Center fill if enabled: display.drawRect( x + BORDER_WIDTH, y + BORDER_WIDTH, width - 2 * BORDER_WIDTH, INDICATOR_HEIGHT - 2 * BORDER_WIDTH, True, utils.BG_DARK if is_light else utils.BG_LIGHT, ) # Top bar display.drawRect( x + BORDER_WIDTH, y, width - 2 * BORDER_WIDTH, BORDER_WIDTH, True, utils.BG_DARK if is_light else utils.BG_LIGHT, ) # Bottom bar display.drawRect( x + BORDER_WIDTH, y + INDICATOR_HEIGHT - BORDER_WIDTH, width - 2 * BORDER_WIDTH, 2, True, utils.BG_DARK if is_light else utils.BG_LIGHT, ) # Left bar display.drawRect( x, y + BORDER_WIDTH, BORDER_WIDTH, INDICATOR_HEIGHT - 2 * BORDER_WIDTH, True, utils.BG_DARK if is_light else utils.BG_LIGHT, ) # Right bar display.drawRect( x + width - BORDER_WIDTH, y + BORDER_WIDTH, BORDER_WIDTH, INDICATOR_HEIGHT - 2 * BORDER_WIDTH, True, utils.BG_DARK if is_light else utils.BG_LIGHT, ) text_centered( x, y + 7, width, text, utils.ARCADE_FONT[8], utils.BG_LIGHT if is_light == enabled else utils.BG_DARK, ) def draw_binary_switch(value, x, y, width, height, is_light): center_x = x + width // 2 sprite_file = ( (utils.JOYSTICK_DARK_OFF if is_light else utils.JOYSTICK_LIGHT_OFF) if value == 0 else (utils.JOYSTICK_DARK_ON if is_light else utils.JOYSTICK_LIGHT_ON) ) sprite_info = display.pngInfo(sprite_file) display.drawPng( center_x - sprite_info[0] // 2, y + 25 + (11 if value == 0 else 0), sprite_file ) draw_binary_control_indicator( "ON", value == 1, center_x - sprite_info[0] // 2, y, sprite_info[0], is_light ) draw_binary_control_indicator( "OFF", value == 0, center_x - sprite_info[0] // 2, y + height - 25, sprite_info[0], is_light, ) def draw_vertical_slider(value, min_val, max_val, x, y, width, height, is_light): VERTICAL_PADDING = 5 LEFT_PADDING = 7 FONT_SIZE = utils.ARCADE_FONT[8] SLIDER_BAR_WIDTH = 3 tick_amount = 1 + max_val - min_val total_text_height = sum( [display.getTextHeight(str(i), FONT_SIZE) for i in range(min_val, max_val + 1)] ) text_spacing = (height - 2 * VERTICAL_PADDING - total_text_height) // ( tick_amount - 1 ) # Draw slider legend for i in range(min_val, max_val + 1): display.drawText( x + LEFT_PADDING, y + VERTICAL_PADDING + (i - min_val) * ((total_text_height // tick_amount) + text_spacing), str(i), utils.BG_DARK if is_light else utils.BG_LIGHT, FONT_SIZE, ) total_text_width = LEFT_PADDING + display.getTextWidth(str(max_val), FONT_SIZE) slider_bar_x = ( x + total_text_width + (width - total_text_width) // 2 - SLIDER_BAR_WIDTH // 2 ) # Draw slider bar display.drawRect( slider_bar_x, y + VERTICAL_PADDING, SLIDER_BAR_WIDTH, height - 2 * VERTICAL_PADDING, True, utils.SLIDER_PURPLE, ) # Draw slider handle slider_handle_data = display.pngInfo(utils.SLIDER_HANDLE_DARK) display.drawPng( slider_bar_x + (SLIDER_BAR_WIDTH // 2) - (slider_handle_data[0] // 2), y + (value - min_val) * ((height - slider_handle_data[1] - VERTICAL_PADDING) // (tick_amount - 1)), utils.SLIDER_HANDLE_DARK if is_light else utils.SLIDER_HANDLE_LIGHT, ) def draw_rotary_switch(value, x, y, min_val, max_val, width, height, is_light): FONT_SIZE = utils.ARCADE_FONT[8] HORIZONTAL_PADDING = 5 MAX_TICK_COUNT = 5 TICK_COUNT = 1 + max_val - min_val if TICK_COUNT not in [3, 5]: raise Exception("Invalid rotary switch provided with value range: [%s, %s]" % (min_val, max_val)) total_text_width = sum( [display.getTextWidth(str(i), FONT_SIZE) for i in range(MAX_TICK_COUNT)] ) text_spacing = (width - 2 * HORIZONTAL_PADDING - total_text_width) // ( MAX_TICK_COUNT - 1 ) for i in range(min_val, max_val + 1): index = i - min_val if TICK_COUNT == 5 else [0, 2, 4][i - min_val] y_pos = y + 15 if index == 0 or index == 4: y_pos += 2 * 12 elif index == 1 or index == 3: y_pos += 12 display.drawText( x + HORIZONTAL_PADDING + index * ((total_text_width // MAX_TICK_COUNT) + text_spacing), y_pos, str(i), utils.BG_DARK if is_light else utils.BG_LIGHT, FONT_SIZE, ) control_sprite = utils.ROTARY_CONTROL(value if TICK_COUNT == 5 else [1, 3, 5][value - min_val], is_light) # Draw control handle rotary_control_data = display.pngInfo(control_sprite) display.drawPng( x + (width // 2 - rotary_control_data[0] // 2), y + 45, control_sprite, ) def draw_control(control, i, control_name): is_light = i % 2 == 0 block_width = utils.DISPLAY_SIZE[0] // 4 x0 = block_width * i y0 = STATUS_BAR_HEIGHT + PROGRESS_BAR_HEIGHT block_height = utils.DISPLAY_SIZE[1] - y0 display.drawRect( x0, y0, block_width, block_height, True, utils.BG_LIGHT if is_light else utils.BG_DARK, ) draw_control_text( control_name, x0, y0, block_width, TEXT_BLOCK_HEIGHT, is_light ) if control["type"] == CONTROL_TYPES[0]: draw_vertical_slider( control["value"], control["min"], control["max"], x0, y0 + TEXT_BLOCK_HEIGHT, block_width, block_height - TEXT_BLOCK_HEIGHT, is_light, ) elif control["type"] == CONTROL_TYPES[1]: draw_rotary_switch( control["value"], x0, y0 + TEXT_BLOCK_HEIGHT, control["min"], control["max"], block_width, block_height - TEXT_BLOCK_HEIGHT, is_light, ) elif control["type"] == CONTROL_TYPES[2]: draw_binary_switch( control["value"], x0, y0 + TEXT_BLOCK_HEIGHT, block_width, block_height - TEXT_BLOCK_HEIGHT, is_light, ) # Below can be removed once all game elements are integrated - this is only for testing/demo purposes # state = { # "totalTime": 8, # "remainingTime": 8, # "assignment": "Set power plant level to 5", # "controls": [ # { # "type": CONTROL_TYPES[0], # "tickAmount": 6, # "value": 3, # "title": "Money Supply", # }, # {"type": CONTROL_TYPES[1], "value": 2, "title": "Power Plant Level"}, # { # "type": CONTROL_TYPES[2], # "value": 0, # "title": "Traffic Light Control", # }, # { # "type": CONTROL_TYPES[2], # "value": 1, # "title": "Flood Control", # }, # ], # } # # # def set_vertical_switch(up): # vertical_switch = state["controls"][0] # # if not ( # ((not up) and vertical_switch["value"] == vertical_switch["tickAmount"] - 1) # or (up and vertical_switch["value"] == 0) # ): # vertical_switch["value"] += -1 if up else 1 # # state["controls"][0] = vertical_switch # # draw_game_screen(state) # # # def set_rotary_switch(left): # rotary_switch = state["controls"][1] # # if not ( # ((not left) and rotary_switch["value"] == 4) # or (left and rotary_switch["value"] == 0) # ): # rotary_switch["value"] += -1 if left else 1 # # state["controls"][1] = rotary_switch # # draw_game_screen(state) # # # def toggle_binary_switch(left): # binary_switch = state["controls"][2 if left else 3] # # binary_switch["value"] = int(not binary_switch["value"]) # # state["controls"][2 if left else 3] = binary_switch # # draw_game_screen(state) # # # # def draw_demo_game(): # buttons.attach( # buttons.BTN_UP, # lambda pressed: set_vertical_switch(True) if pressed else 0, # ) # buttons.attach( # buttons.BTN_DOWN, # lambda pressed: set_vertical_switch(False) if pressed else 0, # ) # buttons.attach( # buttons.BTN_LEFT, lambda pressed: set_rotary_switch(True) if pressed else 0 # ) # buttons.attach( # buttons.BTN_RIGHT, lambda pressed: set_rotary_switch(False) if pressed else 0 # ) # buttons.attach( # buttons.BTN_A, # lambda pressed: toggle_binary_switch(False) if pressed else 0, # ) # buttons.attach( # buttons.BTN_B, # lambda pressed: toggle_binary_switch(True) if pressed else 0, # ) # # buttons.attach(buttons.BTN_HOME, lambda pressed: main.main_menu() if pressed else 0) # # draw_game_screen(state)