├── utils ├── battery │ ├── __init__.py │ └── battery.py ├── speaker │ ├── __init__.py │ └── speaker.py ├── button │ ├── __init__.py │ └── button.py ├── lcd │ ├── __init__.py │ ├── colors.py │ ├── fonts.py │ ├── README.md │ ├── m5stack.py │ ├── main.py │ ├── LICENSE │ ├── glcdfont.py │ ├── tt14.py │ ├── ili934xnew.py │ ├── tt24.py │ └── font_to_py.py ├── __init__.py └── go_consts.py ├── game8266_I2C.fzz ├── game8266_I2C.jpg ├── game8266_SPI.fzz ├── game8266_SPI.jpg ├── examples ├── README.md ├── hello_world │ └── hello_world.py ├── led │ └── led.py ├── battery │ └── battery.py ├── led_pwm │ └── led_pwm.py ├── speaker │ └── speaker.py └── button │ └── button.py ├── btntest.py ├── menu.py ├── README.md ├── pong.py ├── invader.py ├── snake.py ├── game32.py ├── tetris.py ├── breakout.py └── game8266.py /utils/battery/__init__.py: -------------------------------------------------------------------------------- 1 | from .battery import Battery -------------------------------------------------------------------------------- /utils/speaker/__init__.py: -------------------------------------------------------------------------------- 1 | from .speaker import Speaker -------------------------------------------------------------------------------- /utils/button/__init__.py: -------------------------------------------------------------------------------- 1 | from .button import Button 2 | -------------------------------------------------------------------------------- /game8266_I2C.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shivasiddharth/gameESP-micropython/master/game8266_I2C.fzz -------------------------------------------------------------------------------- /game8266_I2C.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shivasiddharth/gameESP-micropython/master/game8266_I2C.jpg -------------------------------------------------------------------------------- /game8266_SPI.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shivasiddharth/gameESP-micropython/master/game8266_SPI.fzz -------------------------------------------------------------------------------- /game8266_SPI.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shivasiddharth/gameESP-micropython/master/game8266_SPI.jpg -------------------------------------------------------------------------------- /utils/lcd/__init__.py: -------------------------------------------------------------------------------- 1 | from .ili934xnew import ILI9341 2 | from .colors import * 3 | from .fonts import GLCDFONT, TT14, TT24, TT32 4 | -------------------------------------------------------------------------------- /utils/lcd/colors.py: -------------------------------------------------------------------------------- 1 | RED = const(0xF800) 2 | GREEN = const(0x07E0) 3 | BLUE = const(0x001F) 4 | BLACK = const(0x0000) 5 | WHITE = const(0xFFFF) -------------------------------------------------------------------------------- /utils/lcd/fonts.py: -------------------------------------------------------------------------------- 1 | from . import glcdfont 2 | from . import tt14, tt24, tt32 3 | 4 | GLCDFONT = glcdfont 5 | TT14 = tt14 6 | TT24 = tt24 7 | TT32 = tt32 8 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | MicroPython Examples for ODROID-GO 2 | ===== 3 | 4 | These examples are not provided as a module. 5 | 6 | Copy and paste an example code to try. -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .go_consts import * 2 | from .lcd import ILI9341, colors, fonts 3 | from .button import Button 4 | from .speaker import Speaker 5 | from .battery import Battery 6 | -------------------------------------------------------------------------------- /examples/hello_world/hello_world.py: -------------------------------------------------------------------------------- 1 | from odroid_go import GO 2 | 3 | GO.lcd.set_font(GO.lcd.fonts.TT24) 4 | GO.lcd.set_color(GO.lcd.colors.GREEN, GO.lcd.colors.BLACK) 5 | GO.lcd.print("Hello, ODROID-GO") 6 | -------------------------------------------------------------------------------- /examples/led/led.py: -------------------------------------------------------------------------------- 1 | from machine import Pin 2 | from micropython import const 3 | 4 | import time 5 | 6 | LED_PIN = const(2) 7 | led = Pin(LED_PIN, Pin.OUT) 8 | 9 | while True: 10 | led.value(1) 11 | time.sleep(0.5) 12 | led.value(0) 13 | time.sleep(0.5) 14 | -------------------------------------------------------------------------------- /examples/battery/battery.py: -------------------------------------------------------------------------------- 1 | from odroid_go import GO 2 | import time 3 | 4 | GO.lcd.set_font(GO.lcd.fonts.TT24) 5 | 6 | 7 | def show_battery_voltage(): 8 | GO.lcd.erase() 9 | GO.lcd.set_pos(0, 0) 10 | 11 | GO.lcd.print("Current Voltage: " + str(GO.battery.get_voltage())) 12 | 13 | 14 | while True: 15 | show_battery_voltage() 16 | 17 | time.sleep(1) 18 | -------------------------------------------------------------------------------- /utils/lcd/README.md: -------------------------------------------------------------------------------- 1 | # Micropython Driver for ILI9341 display 2 | 3 | 4 | This has been tested on an M5Stack module using the standard esp32 micropython port. The default font is the Adafruit glcdfont and additional fonts can be generated by a very slightly modified version of Peter Hinch's font-to-py program which includes a function in font file to return the pixel width of a string of characters. 5 | 6 | ![m5stack](image/m5stack.jpg) 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/led_pwm/led_pwm.py: -------------------------------------------------------------------------------- 1 | from machine import Pin, PWM 2 | from micropython import const 3 | 4 | import time 5 | 6 | LED_PIN = const(2) 7 | led = Pin(LED_PIN, Pin.OUT) 8 | pwm1 = PWM(led, freq=1000, duty=0) 9 | 10 | while True: 11 | for dValue in range(0, 1023, 100): 12 | pwm1.duty(dValue) 13 | time.sleep(0.1) 14 | 15 | for dValue in range(1023, 0, -100): 16 | pwm1.duty(dValue) 17 | time.sleep(0.1) 18 | -------------------------------------------------------------------------------- /examples/speaker/speaker.py: -------------------------------------------------------------------------------- 1 | from odroid_go import GO 2 | 3 | GO.lcd.set_font(GO.lcd.fonts.TT24) 4 | GO.lcd.print("ODROID-GO speaker test:") 5 | 6 | GO.speaker.set_volume(3) 7 | 8 | 9 | while True: 10 | GO.update() 11 | 12 | if GO.btn_a.was_pressed(): 13 | GO.lcd.print("was_pressed: A") 14 | GO.speaker.beep() 15 | 16 | if GO.btn_b.was_pressed(): 17 | GO.lcd.print("was_pressed: B") 18 | GO.speaker.tone(3000, 2) 19 | -------------------------------------------------------------------------------- /utils/lcd/m5stack.py: -------------------------------------------------------------------------------- 1 | 2 | # This file is part of MicroPython M5Stack package 3 | # Copyright (c) 2017 Mika Tuupola 4 | # 5 | # Licensed under the MIT license: 6 | # http://www.opensource.org/licenses/mit-license.php 7 | # 8 | # Project home: 9 | # https://github.com/tuupola/micropython-m5stacj 10 | 11 | from micropython import const 12 | 13 | BUTTON_A_PIN = const(39) 14 | BUTTON_B_PIN = const(38) 15 | BUTTON_C_PIN = const(37) 16 | SPEAKER_PIN = const(25) 17 | 18 | TFT_LED_PIN = const(32) 19 | TFT_DC_PIN = const(27) 20 | TFT_CS_PIN = const(14) 21 | TFT_MOSI_PIN = const(23) 22 | TFT_CLK_PIN = const(18) 23 | TFT_RST_PIN = const(33) 24 | TFT_MISO_PIN = const(19) 25 | -------------------------------------------------------------------------------- /utils/go_consts.py: -------------------------------------------------------------------------------- 1 | from micropython import const 2 | 3 | 4 | TFT_DC_PIN = const(21) 5 | TFT_CS_PIN = const(5) 6 | TFT_LED_PIN = const(14) 7 | TFT_MOSI_PIN = const(23) 8 | TFT_MISO_PIN = const(19) 9 | TFT_SCLK_PIN = const(18) 10 | 11 | BUTTON_A_PIN = const(32) 12 | BUTTON_B_PIN = const(33) 13 | BUTTON_MENU_PIN = const(13) 14 | BUTTON_SELECT_PIN = const(27) 15 | BUTTON_VOLUME_PIN = const(0) 16 | BUTTON_START_PIN = const(39) 17 | BUTTON_JOY_Y_PIN = const(35) 18 | BUTTON_JOY_X_PIN = const(34) 19 | BUTTON_DEBOUNCE_MS = const(5) 20 | 21 | SPEAKER_PIN = const(26) 22 | SPEAKER_DAC_PIN = const(25) 23 | SPEAKER_TONE_CHANNEL = const(0) 24 | 25 | BATTERY_PIN = const(36) 26 | BATTERY_RESISTANCE_NUM = const(2) 27 | -------------------------------------------------------------------------------- /utils/lcd/main.py: -------------------------------------------------------------------------------- 1 | # test of printing multiple fonts to the ILI9341 on an M5Stack using H/W SP 2 | # MIT License; Copyright (c) 2017 Jeffrey N. Magee 3 | 4 | from ili934xnew import ILI9341, color565 5 | from machine import Pin, SPI 6 | import m5stack 7 | import tt14 8 | import glcdfont 9 | import tt14 10 | import tt24 11 | import tt32 12 | 13 | fonts = [glcdfont,tt14,tt24,tt32] 14 | 15 | text = 'Now is the time for all good men to come to the aid of the party.' 16 | 17 | power = Pin(m5stack.TFT_LED_PIN, Pin.OUT) 18 | power.value(1) 19 | 20 | spi = SPI( 21 | 2, 22 | baudrate=40000000, 23 | miso=Pin(m5stack.TFT_MISO_PIN), 24 | mosi=Pin(m5stack.TFT_MOSI_PIN), 25 | sck=Pin(m5stack.TFT_CLK_PIN)) 26 | 27 | display = ILI9341( 28 | spi, 29 | cs=Pin(m5stack.TFT_CS_PIN), 30 | dc=Pin(m5stack.TFT_DC_PIN), 31 | rst=Pin(m5stack.TFT_RST_PIN)) 32 | display.erase() 33 | display.set_pos(0,0) 34 | for ff in fonts: 35 | display.set_font(ff) 36 | display.print(text) 37 | 38 | 39 | -------------------------------------------------------------------------------- /utils/lcd/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jeffrey N. Magee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /utils/battery/battery.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple battery module for Micropython, only for ODROID-GO (ESP32). 3 | 4 | Created on: 2018. 7. 13 5 | Author: Joshua Yang (joshua.yang@hardkernel.com) 6 | """ 7 | 8 | from machine import Pin, ADC 9 | 10 | 11 | class Battery: 12 | """ 13 | TODO: Have to calibrate the results voltage using Vref on efuse. 14 | But the necessary functions seem not to be implemented to MicroPython yet. 15 | * esp_adc_cal_characterize() 16 | * esp_adc_cal_raw_to_voltage() 17 | 18 | This module calculate current battery voltage roughly for now. 19 | """ 20 | 21 | _adc1_pin = object() 22 | _sampling_count = 64 23 | _battery_resistance_num = 0 24 | 25 | def __init__(self, pin, resistance_num, width, atten): 26 | self._adc1_pin = ADC(Pin(pin)) 27 | self._adc1_pin.width(width) 28 | self._adc1_pin.atten(atten) 29 | 30 | self._battery_resistance_num = resistance_num 31 | 32 | def get_voltage(self): 33 | reading = 0 34 | for i in range(0, self._sampling_count): 35 | reading += self._adc1_pin.read() 36 | 37 | reading /= self._sampling_count 38 | 39 | return reading * self._battery_resistance_num / 1000 40 | -------------------------------------------------------------------------------- /examples/button/button.py: -------------------------------------------------------------------------------- 1 | from odroid_go import GO 2 | import time 3 | 4 | GO.lcd.set_font(GO.lcd.fonts.TT14) 5 | 6 | 7 | def display_buttons(): 8 | GO.lcd.erase() 9 | GO.lcd.set_pos(0, 0) 10 | 11 | GO.lcd.print("/* Direction Pad */") 12 | GO.lcd.print("Joy-Y-Up: " + ("Pressed" if GO.btn_joy_y.is_axis_pressed() == 2 else "")) 13 | GO.lcd.print("Joy-Y-Down: " + ("Pressed" if GO.btn_joy_y.is_axis_pressed() == 1 else "")) 14 | GO.lcd.print("Joy-X-Left: " + ("Pressed" if GO.btn_joy_x.is_axis_pressed() == 2 else "")) 15 | GO.lcd.print("Joy-X-Right: " + ("Pressed" if GO.btn_joy_x.is_axis_pressed() == 1 else "")) 16 | GO.lcd.print("") 17 | GO.lcd.print("/* Function Key */") 18 | GO.lcd.print("Menu: " + ("Pressed" if GO.btn_menu.is_pressed() == 1 else "")) 19 | GO.lcd.print("Volume: " + ("Pressed" if GO.btn_volume.is_pressed() == 1 else "")) 20 | GO.lcd.print("Select: " + ("Pressed" if GO.btn_select.is_pressed() == 1 else "")) 21 | GO.lcd.print("Start: " + ("Pressed" if GO.btn_start.is_pressed() == 1 else "")) 22 | GO.lcd.print("") 23 | GO.lcd.print("/* Actions */") 24 | GO.lcd.print("B: " + ("Pressed" if GO.btn_b.is_pressed() == 1 else "")) 25 | GO.lcd.print("A: " + ("Pressed" if GO.btn_a.is_pressed() == 1 else "")) 26 | 27 | 28 | while True: 29 | GO.update() 30 | display_buttons() 31 | 32 | time.sleep(1) 33 | -------------------------------------------------------------------------------- /utils/speaker/speaker.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple speaker module for Micropython (ESP32). 3 | 4 | Created on: 2018. 7. 10 5 | Author: Joshua Yang (joshua.yang@hardkernel.com) 6 | """ 7 | 8 | from machine import PWM, Pin 9 | import utime 10 | 11 | 12 | class Speaker: 13 | _speaker_pin = object() 14 | _speaker_pwm = object() 15 | _dac_pin = object() 16 | _beep_duration = 0 17 | _beep_frequency = 0 18 | _volume_duty = 0 19 | 20 | def __init__(self, pin, dac_pin, frequency=262, duration=0, volume=1): 21 | self._speaker_pin = Pin(pin, Pin.OUT, value=1) 22 | self._speaker_pwm = PWM(self._speaker_pin, duty=0) 23 | self._dac_pin = Pin(dac_pin, Pin.OUT, value=1) 24 | 25 | self.set_beep(frequency, duration) 26 | self.set_volume(volume) 27 | 28 | def _dac_switch(self, switch=None): 29 | if switch is None: 30 | self._dac_pin.value(1 if self._dac_pin.value() == 0 else 0) 31 | else: 32 | self._dac_pin.value(switch) 33 | 34 | # TODO: It should run as in non-blocking way (async, thread, ...) 35 | def _play_tone(self, frequency=None, duration=None): 36 | self._speaker_pwm.freq(self._beep_frequency if frequency is None else frequency) 37 | self._speaker_pwm.duty(self._volume_duty) 38 | 39 | utime.sleep(self._beep_duration if duration is None else duration) 40 | self._speaker_pwm.duty(0) 41 | 42 | def tone(self, frequency=None, duration=None): 43 | self._play_tone(frequency, duration) 44 | 45 | def beep(self): 46 | self.tone() 47 | 48 | def mute(self): 49 | self._dac_switch(0) 50 | 51 | def toggle_mute(self, switch=None): 52 | self._dac_switch(switch) 53 | 54 | def set_volume(self, volume=None): 55 | if volume is None: 56 | print(str(self._volume_duty) + "/" + str(self._volume_duty / 1023 * 100) + " %") 57 | elif not 0 <= volume <= 10: 58 | print("Volume value must be from 0 to 10") 59 | else: 60 | self._dac_switch(1) 61 | self._volume_duty = round(volume * 10 * 10.23) 62 | 63 | def set_beep(self, frequency=None, duration=None): 64 | if frequency is None and duration is None: 65 | print(str(self._beep_frequency) + "/" + str(self._beep_duration)) 66 | else: 67 | self._beep_frequency = self._beep_frequency if frequency is None else frequency 68 | self._beep_duration = self._beep_duration if duration is None else duration 69 | 70 | # TODO: Not implemented yet 71 | def play_music(self, music_data, sample_rate): 72 | pass 73 | -------------------------------------------------------------------------------- /btntest.py: -------------------------------------------------------------------------------- 1 | # btntests.py 2 | # button , paddle, speaker and display tester for game board 3 | # Use common game module "gameESP.py" for ESP8266 or ESP32 4 | # by Billy Cheung 2019 10 26 5 | # 6 | # ESP32 Game board (follow same pin layout as the Odroid_go) 7 | 8 | # OLED SPI 9 | # ======== 10 | # VCC - 3V3 11 | # GND - GND 12 | # D0/SCK - IO18-VSPI-SCK 13 | # D1/MOSI - IO23-VSPI-MOSI 14 | # RES - IO4 for ESP32 15 | # DC - IO21 16 | # CS - IO5-VSPI CS0 17 | # LED/BLK - IO14 18 | 19 | # MISO - IO19-VSPI-MISO 20 | 21 | # Audio 22 | # ====== 23 | # Speaker- GND 24 | # Speaker+ - 10K VR- IO26 25 | 26 | # Paddle (10K VR) 27 | # ====== 28 | # GND 29 | # VN/IO39 30 | # -VCC 31 | # 32 | # D-PAD Buttons 33 | # ============= 34 | # tie one end to 3V3 35 | # UP IO35-10K-GND 36 | # Down-10K IO35 37 | # Left IO34-10K-GND 38 | # Right-10K IO34 39 | 40 | # Other Buttons 41 | # ============ 42 | # tie one end to GND 43 | # Menu IO13 44 | # Volume IO00-10K-3v3 45 | # Select IO27 46 | # Start IO39(VN)-10K-3v3 47 | # B IO33 48 | # A IO32 49 | # 50 | 51 | import gc 52 | import sys 53 | gc.collect() 54 | print (gc.mem_free()) 55 | import network 56 | import utime 57 | from utime import sleep_ms,ticks_ms, ticks_us, ticks_diff 58 | from machine import ADC 59 | # all dislplay, buttons, paddle, sound logics are in game32.mpy module 60 | from gameESP import * 61 | g=gameESP() 62 | tone_dur = 20 63 | 64 | # while not (pressed(btnL) and pressed(btnA)): 65 | while True : 66 | 67 | 68 | 69 | g.display.fill(0) 70 | g.display.text("B+L=Exit", 40,54,1) 71 | 72 | g.getBtn() 73 | g.setVol() 74 | 75 | if g.ESP32 : 76 | # get ESP32 button's ADC value 77 | g.display.text ("X:{}".format(g.adcX.read()), 0,10,1) 78 | g.display.text ("Y:{}".format(g.adcY.read()), 60,10,1) 79 | 80 | g.display.text ("P:{}".format(g.getPaddle()), 0,0,1) 81 | g.display.text ("Vol:{}".format(g.vol),60 ,0,1) 82 | else : 83 | #get ESP8266 button's ADC valu 84 | g.display.text ("Btn:{}".format(g.adc.read()), 0,0,1) 85 | g.display.text ("Vol:{}".format(g.vol),60 ,0,1) 86 | 87 | g.display.text ("P:{}".format(g.getPaddle()), 0,10,1) 88 | g.display.text ("P2:{}".format(g.getPaddle2()), 60,10,1) 89 | 90 | 91 | 92 | 93 | 94 | 95 | if g.pressed(g.btnB) and g.justReleased(g.btnL): 96 | g.display.text("Bye!",10, 30,1) 97 | g.playTone('e4', tone_dur) 98 | g.playTone('c4', tone_dur) 99 | g.display.show() 100 | break 101 | 102 | if g.pressed (g.btnU): 103 | g.display.text("U",20, 20,1) 104 | g.playTone('c4', tone_dur) 105 | else : 106 | g.display.text(":",20, 20,1) 107 | if g.pressed(g.btnL): 108 | g.display.text("L",0, 35,1) 109 | g.playTone('d4', tone_dur) 110 | else : 111 | g.display.text(":",0, 35,1) 112 | if g.pressed(g.btnR): 113 | g.display.text("R",40, 35,1) 114 | g.playTone('e4', tone_dur) 115 | else : 116 | g.display.text(":",40, 35,1) 117 | 118 | if g.pressed(g.btnD): 119 | g.display.text("D",20, 50,1) 120 | g.playTone('f4', tone_dur) 121 | 122 | else : 123 | g.display.text(":",20, 50,1) 124 | if g.pressed(g.btnA): 125 | g.display.text("A",80, 40,1) 126 | g.playTone('c5', tone_dur) 127 | else : 128 | g.display.text(":",80, 40,1) 129 | if g.pressed(g.btnB): 130 | g.display.text("B",100, 30,1) 131 | g.playTone('d5', tone_dur) 132 | else : 133 | g.display.text(":",100, 30,1) 134 | 135 | 136 | 137 | g.display.show() 138 | sleep_ms(5) 139 | 140 | if g.ESP32 : 141 | g.deinit() 142 | del sys.modules["gameESP"] 143 | gc.collect() 144 | -------------------------------------------------------------------------------- /menu.py: -------------------------------------------------------------------------------- 1 | # menu.py. 2 | # startup menu and luancher for other python programs 3 | # 4 | # Use common game module "gameESP.py" for ESP8266 or ESP32 5 | # by Billy Cheung 2019 10 26 6 | 7 | import os 8 | import sys 9 | import gc 10 | gc.collect() 11 | print (gc.mem_free()) 12 | from machine import Pin, ADC 13 | from utime import sleep_ms, ticks_ms, ticks_diff 14 | module_name = "" 15 | tone_dur = 10 16 | 17 | def do_menu (g) : 18 | global module_name, vol, max_vol 19 | 20 | # all dislplay, buttons, paddle, sound logics are in game8266.mpy module 21 | 22 | 23 | SKIP_NAMES = ("boot", "main", "menu","gameESP") 24 | 25 | 26 | files = [item[0] for item in os.ilistdir(".") if item[1] == 0x8000] 27 | 28 | 29 | module_names = [ 30 | filename.rsplit(".", 1)[0] 31 | for filename in files 32 | if (filename.endswith(".py") or filename.endswith(".mpy") ) and not filename.startswith("_") 33 | ] 34 | module_names = [module_name for module_name in module_names if not module_name in SKIP_NAMES] 35 | module_names.sort() 36 | tot_file = len(module_names) 37 | tot_rows = const(5) 38 | screen_pos = 0 39 | file_pos = 0 40 | 41 | 42 | g.display.fill(0) 43 | g.display.text('Micropython Menu', 0, 0, 1) 44 | g.display.text('U/D to Select', 0, 20, 1) 45 | g.display.text('A to Luanch', 0, 30, 1) 46 | g.display.text('B+L to exit', 0, 40, 1) 47 | g.display.text('Press U to start', 0, 50, 1) 48 | g.display.show() 49 | sleep_ms(1000) 50 | 51 | # wait till all buttons are released 52 | g.getBtn() 53 | while not g.Btns : 54 | sleep_ms(10) 55 | g.getBtn() 56 | 57 | luanched = False 58 | while not luanched : 59 | gc.collect() 60 | g.display.fill(0) 61 | g.display.text('M:{}'.format(gc.mem_free()), 0, 0, 1) 62 | g.display.rect(90,0, g.max_vol*4+2,6,1) 63 | g.display.fill_rect(91,1, g.vol * 4,4,1) 64 | i = 0 65 | for j in range (file_pos, min(file_pos+tot_rows, tot_file)) : 66 | current_row = 12 + 10 *i 67 | if i == screen_pos : 68 | g.display.fill_rect(0, current_row, 127, 10, 1) 69 | g.display.text(str(j) + " " + module_names[j],0 , current_row, 0) 70 | else: 71 | g.display.fill_rect(0, current_row, 127, 10, 0) 72 | g.display.text(str(j) + " " + module_names[j], 0, current_row, 1) 73 | i+=1 74 | g.display.show() 75 | g.getBtn() 76 | 77 | if g.justPressed(g.btnU): 78 | if g.pressed(g.btnB): 79 | g.vol = min (g.vol+1, g.max_vol) 80 | g.playTone('e4', tone_dur) 81 | else : 82 | if screen_pos > 0 : 83 | screen_pos -= 1 84 | else : 85 | if file_pos > 0 : 86 | file_pos = max (0, file_pos - tot_rows) 87 | screen_pos=tot_rows-1 88 | g.playTone('c4', tone_dur) 89 | 90 | 91 | if g.justPressed(g.btnD): 92 | if g.pressed(g.btnB): 93 | g.vol= max (g.vol-1, 0) 94 | g.playTone('d4', tone_dur) 95 | else : 96 | if screen_pos < min(tot_file - file_pos - 1, tot_rows -1) : 97 | screen_pos = min(tot_file-1, screen_pos + 1) 98 | else : 99 | if file_pos + tot_rows < tot_file : 100 | file_pos = min (tot_file, file_pos + tot_rows) 101 | screen_pos=0 102 | g.playTone('e4', tone_dur) 103 | 104 | if g.justReleased(g.btnA): 105 | g.playTone('c5', tone_dur) 106 | g.display.fill(0) 107 | g.display.text("luanching " , 5, 20, 1) 108 | g.display.text(module_names[file_pos + screen_pos], 5, 40, 1) 109 | g.display.show() 110 | sleep_ms(1000) 111 | module_name = module_names[file_pos + screen_pos] 112 | return True 113 | 114 | if g.pressed(g.btnB) and g.justPressed(g.btnL): 115 | g.playTone('d5', tone_dur) 116 | luanched = True 117 | g.display.fill(0) 118 | g.display.text("Menu exited", 5, 24, 1) 119 | g.display.show() 120 | return False 121 | g.display.show() 122 | sleep_ms(10) 123 | 124 | 125 | go_on = True 126 | from gameESP import * 127 | g=gameESP() 128 | ESP32 = g.ESP32 129 | 130 | while go_on : 131 | 132 | go_on = do_menu(g) 133 | if ESP32 : 134 | g.deinit() 135 | del sys.modules["gameESP"] 136 | 137 | if go_on : 138 | gc.collect() 139 | module = __import__(module_name) 140 | del sys.modules[module_name] 141 | gc.collect() 142 | 143 | if ESP32 : 144 | from gameESP import * 145 | g=gameESP() 146 | -------------------------------------------------------------------------------- /utils/button/button.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Micropython port of Arduino Button Library. 3 | 4 | Created on: 2018. 7. 6 5 | Author: Joshua Yang (joshua.yang@hardkernel.com) 6 | 7 | /*----------------------------------------------------------------------* 8 | * Arduino Button Library v1.0 * 9 | * Jack Christensen May 2011, published Mar 2012 * 10 | * * 11 | * Library for reading momentary contact switches like tactile button * 12 | * switches. Intended for use in state machine constructs. * 13 | * Use the read() function to read all buttons in the main loop, * 14 | * which should execute as fast as possible. * 15 | * * 16 | * This work is licensed under the Creative Commons Attribution- * 17 | * ShareAlike 3.0 Unported License. To view a copy of this license, * 18 | * visit http://creativecommons.org/licenses/by-sa/3.0/ or send a * 19 | * letter to Creative Commons, 171 Second Street, Suite 300, * 20 | * San Francisco, California, 94105, USA. * 21 | *----------------------------------------------------------------------*/ 22 | """ 23 | 24 | from machine import Pin, ADC 25 | import utime 26 | 27 | 28 | class Button: 29 | _pin = object() 30 | _invert = 0 31 | _state = 0 32 | _last_state = 0 33 | _changed = 0 34 | _axis = 0 35 | _time = 0 36 | _last_time = 0 37 | _last_change = 0 38 | _db_time = 0 39 | 40 | _debug_mode = False 41 | 42 | def __init__(self, pin, invert, db_time): 43 | if pin < 34 : 44 | self._pin = Pin(pin, Pin.IN, Pin.PULL_UP) 45 | else : 46 | self._pin = Pin(pin, Pin.IN) 47 | self._invert = invert 48 | self._db_time = db_time 49 | 50 | self._state = self._pin.value() 51 | if self._invert != 0: 52 | self._state = not self._state 53 | self._time = utime.ticks_ms() 54 | self._last_state = self._state 55 | self._last_time = self._time 56 | self._last_change = self._time 57 | 58 | # self._debug_mode = True 59 | 60 | def _debug_message(self, results): 61 | if self._debug_mode: 62 | print(results) 63 | 64 | def read(self): 65 | ms = utime.ticks_ms() 66 | pin_val = self._pin.value() 67 | 68 | if self._invert != 0: 69 | pin_val = not pin_val 70 | 71 | if ms - self._last_change < self._db_time: 72 | self._last_time = self._time 73 | self._time = ms 74 | self._changed = 0 75 | 76 | return self._state 77 | else: 78 | self._last_time = self._time 79 | self._last_state = self._state 80 | self._state = pin_val 81 | self._time = ms 82 | 83 | if self._state != self._last_state: 84 | self._last_change = ms 85 | self._changed = 1 86 | else: 87 | self._changed = 0 88 | 89 | return self._state 90 | 91 | def read_axis(self): 92 | apin = ADC(self._pin) 93 | apin.atten(ADC.ATTN_11DB) 94 | val = apin.read() 95 | 96 | ms = utime.ticks_ms() 97 | pin_val = 0 98 | 99 | self._debug_message("val: " + str(val)) 100 | 101 | if val > 3900: 102 | pin_val = 1 103 | self._axis = 2 104 | elif 1500 < val < 2000: 105 | pin_val = 1 106 | self._axis = 1 107 | else: 108 | pin_val = 0 109 | self._axis = 0 110 | 111 | if self._invert == 0: 112 | pin_val = not pin_val 113 | 114 | if ms - self._last_change < self._db_time: 115 | self._last_time = self._time 116 | self._time = ms 117 | self._changed = 0 118 | else: 119 | self._last_time = self._time 120 | self._last_state = self._state 121 | self._state = pin_val 122 | self._time = ms 123 | 124 | if self._state != self._last_state: 125 | self._last_state = ms 126 | self._changed = 1 127 | else: 128 | self._changed = 0 129 | 130 | return self._state 131 | 132 | def is_pressed(self): 133 | return 0 if self._state == 0 else 1 134 | 135 | def is_axis_pressed(self): 136 | return self._axis if self._state else 0 137 | 138 | def is_released(self): 139 | return 1 if self._state == 0 else 0 140 | 141 | def was_pressed(self): 142 | return self._state and self._changed 143 | 144 | def was_axis_pressed(self): 145 | return self._axis if self._state and self._changed else 0 146 | 147 | def was_released(self): 148 | return (not self._state) and self._changed 149 | 150 | def pressed_for(self, ms): 151 | return 1 if (self._state == 1 and self._time - self._last_change >= ms) else 0 152 | 153 | def released_for(self, ms): 154 | return 1 if (self._state == 0 and self._time - self._last_change >= ms) else 0 155 | 156 | def last_change(self): 157 | return self._last_change 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gameESP-micropython 2 | # Simple MicroPython game modules and sample games for ESP8266 and ESP32 3 | # 4 | # gameESP.py for esp8266 or esp32 5 | # if you are using esp8266 boards, copy game8266.py as gameESP.py or use mpy-cross to compile to gameESP.mpy 6 | # if you are using esp32 boards, copy game32.py as gameESP.py or use mpy-cross to compile to gameESP.mpy 7 | # 8 | # common micropython module for ESP8266 game board designed by Billy Cheung (c) 2019 08 31 9 | # --usage-- 10 | # Using this common micropython game module, you can write micropython games to run 11 | # either on the SPI OLED or I2C OLED without chaning a line of code. 12 | # You only need to set the following line in gameESP.py file at the __init__ function 13 | # self.useSPI = True # for SPI display , with buttons read through ADC 14 | # self.useSPI = False # for I2C display, and individual hard buttons 15 | # 16 | # Note: esp8266 is very bad at running .py micropython source code files 17 | # with its very limited CPU onboard memory of 32K 18 | # so to run any program with > 300 lines of micropython codes combined (including all modules), 19 | # you need to convert source files into byte code first to avoid running out of memory. 20 | # Install a version of the mpy-cross micropython pre-compiler that can run in your system (available from github). 21 | # Type this command to convert gameESP.py to the byte code file gameESP.mpy using mpy-cross. 22 | # mpy-cross gameESP.py 23 | # then copy the gameESP.mpy file to the micropython's import directory on the flash 24 | # create your game and leaverge the functions to display, read buttons and paddle and make sounds 25 | # from the gameESP class module. 26 | # Add this line to your micropython game source code (examples attached, e.g. invader.py) 27 | # from gameESP import gameESP, Rect 28 | # g=gameESP() 29 | # 30 | # 31 | # 32 | #----------------------------------------- 33 | # ESP8266 SPI SSD1306 version of game board layout 34 | # ---------------------------------------- 35 | ![Game8266%20SPI](game8266_SPI.jpg) 36 | # micropython game hat module to use SSD1306 SPI OLED, 6 buttons and a paddle 37 | # SPI display runs 5 times faster than I2C display in micropython and you need this speeds 38 | # for games with many moving graphics (e.g. space invdader, breakout). 39 | # 40 | # Buttons are read through A0 using many resistors in a Voltage Divider circuit 41 | # ESP8266 (node MCU D1 mini) micropython 42 | # 43 | # SPI OLED 44 | # GND 45 | # VCC 46 | # D0/Sck - D5 (=GPIO14=HSCLK) 47 | # D1/MOSI- D7 (=GPIO13=HMOSI) 48 | # RES - D0 (=GPIO16) 49 | # DC - D4 (=GPIO2) 50 | # CS - Hard wired to ground. 51 | # Speaker 52 | # GPIO15  D8 Speaker 53 | # n.c. - D6 (=GPIO12=HMISO) 54 | # 55 | # The ADC(0) (aka A0) is used to read both paddles and Buttons 56 | # these two pins together control whether buttons or paddle will be read 57 | # GPIO5   D1—— PinBtn 58 | # GPIO4   D2—— pinPaddle 59 | # GPIO0 D3-- PinPaddle2 60 | 61 | # To read buttons - Pin.Btn.On() Pin.Paddle.off() Pin.Paddle2.off() 62 | # To read paddle - Pin.Btn.Off() Pin.Paddle.on() Pin.Paddle2.off() 63 | # To read paddle2 - Pin.Btn.Off() Pin.Paddle.off() Pin.Paddle2.on() 64 | # 65 | # buttons are connected in series to create a voltage dividor 66 | # Each directional and A , B button when pressed will connect that point of 67 | # the voltage dividor to A0 to read the ADC value to determine which button is pressed. 68 | # resistor values are chosen to ensure we have at least a gap of 10 between each button combinations. 69 | # L, R, U, D, can be pressed individually but not toghether. 70 | # A, B, can be pressed invididually but not together. 71 | # any one of A or B, can be pressed together with any one of L,R,U,D 72 | # so you can move the gun using L,R, U,D, while shooting with A or B. 73 | # 74 | # refer to the schematics on my github for how to hook it up 75 | # 76 | # 3.3V-9K-Up-9K-Left-12K-Right-9K-Down-9K-A button-12K-B Button-9K-GND 77 | # 78 | #----------------------------------------- 79 | # ESP8266 I2C SSD1306 version of game board layout 80 | # ---------------------------------------- 81 | ![Game8266%20I2C](game8266_I2C.jpg) 82 | # mocropython game hat module to use SSD1306 I2C OLED, 6 buttons and a paddle 83 | # I2C display runs 5 times slower than I2C display in micropython. 84 | # Games with many moving graphics (e.g. space invdader, breakout) will run slower. 85 | # 86 | # Buttons are read through indvidial GPIO pins (pulled high). 87 | # 88 | # I2C OLED SSD1306 89 | # GPIO4 D2--- SDA OLED 90 | # GPIO5 D1--- SCL OLED 91 | # 92 | # Speaker 93 | # GPIO15  D8 Speaker 94 | # 95 | # Buttons are connect to GND when pressed 96 | # GPIO12  D6—— Left   97 | # GPIO13  D7—— Right     98 | # GPIO14  D5—— UP     99 | # GPIO2   D4——   Down     100 | # GPIO0   D3——   A 101 | # GPIO16   D0——  B 102 | # GPIO16 cannot be pulled high by softeware, connect a 10K resisor to VCC to pull high 103 | # 104 | # 105 | #================================================================================== 106 | # ESP32 Game board 107 | # ----------------- 108 | # The pin layout is exactly the same as that of the Odroid-Go 109 | # so this library can be used on the micropython firmware of the Odroid-Go 110 | # 111 | #------------------------ 112 | # ESP32 OLED SPI SSD1306 113 | # ============== 114 | # VCC - 3.3V 115 | # GND - GND 116 | # D0/SCK - IO18-VSPI-SCK 117 | # D1/MOSI - IO23-VSPI-MOSI 118 | # RES - IO4 for ESP32 119 | # DC - IO21 120 | # CS - IO5-VSPI CS0 121 | # LED/BLK - IO14 122 | # 123 | # MISO - IO19-VSPI-MISO (not required for OLED) 124 | # 125 | # 126 | # TF Card Odroid-go (optional) 127 | # ================ 128 | # CS - IO22 VSPI CS1 129 | # MOSI - IO23 VSPI MOSI 130 | # MISO - IO19 VSPI SCK 131 | # SCK - IO18 VSPI MISO 132 | # 133 | # ESP32 OLED I2C SSD1306 134 | # ================ 135 | # VCC - 3.3V 136 | # GND - GND 137 | # SCL - IO 22 138 | # SDA IO 21 139 | 140 | # Audio 141 | # ====== 142 | # Speaker- - GND 143 | # Speaker+ - 10K VR- IO26 144 | 145 | # Paddle (10K VR) 146 | # ====== 147 | # left GND 148 | # middle VN/IO39 149 | # right VCC 150 | 151 | 152 | # D-PAD Buttons 153 | # ============= 154 | # tie one end to 3V3 155 | # UP IO35-10K-GND 156 | # Down-10K IO35 157 | # Left IO34-10K-GND 158 | # Right-10K IO34 159 | 160 | # Other Buttons 161 | # ============ 162 | # tie one end to GND 163 | # Menu IO13 164 | # Volume IO00-10K-3v3 165 | # Select IO27 166 | # Start IO39(VN)-10K-3v3 167 | # B IO33 168 | # A IO32 169 | 170 | -------------------------------------------------------------------------------- /utils/lcd/glcdfont.py: -------------------------------------------------------------------------------- 1 | # Original Adafruit_GFX 5x7 font 2 | 3 | def height(): 4 | return 8 5 | 6 | def max_width(): 7 | return 6 8 | 9 | def hmap(): 10 | return False 11 | 12 | def reverse(): 13 | return False 14 | 15 | def monospaced(): 16 | return True 17 | 18 | def min_ch(): 19 | return 0 20 | 21 | def max_ch(): 22 | return 255 23 | 24 | 25 | _font = \ 26 | b'\x00\x00\x00\x00\x00'\ 27 | b'\x3E\x5B\x4F\x5B\x3E'\ 28 | b'\x3E\x6B\x4F\x6B\x3E'\ 29 | b'\x1C\x3E\x7C\x3E\x1C'\ 30 | b'\x18\x3C\x7E\x3C\x18'\ 31 | b'\x1C\x57\x7D\x57\x1C'\ 32 | b'\x1C\x5E\x7F\x5E\x1C'\ 33 | b'\x00\x18\x3C\x18\x00'\ 34 | b'\xFF\xE7\xC3\xE7\xFF'\ 35 | b'\x00\x18\x24\x18\x00'\ 36 | b'\xFF\xE7\xDB\xE7\xFF'\ 37 | b'\x30\x48\x3A\x06\x0E'\ 38 | b'\x26\x29\x79\x29\x26'\ 39 | b'\x40\x7F\x05\x05\x07'\ 40 | b'\x40\x7F\x05\x25\x3F'\ 41 | b'\x5A\x3C\xE7\x3C\x5A'\ 42 | b'\x7F\x3E\x1C\x1C\x08'\ 43 | b'\x08\x1C\x1C\x3E\x7F'\ 44 | b'\x14\x22\x7F\x22\x14'\ 45 | b'\x5F\x5F\x00\x5F\x5F'\ 46 | b'\x06\x09\x7F\x01\x7F'\ 47 | b'\x00\x66\x89\x95\x6A'\ 48 | b'\x60\x60\x60\x60\x60'\ 49 | b'\x94\xA2\xFF\xA2\x94'\ 50 | b'\x08\x04\x7E\x04\x08'\ 51 | b'\x10\x20\x7E\x20\x10'\ 52 | b'\x08\x08\x2A\x1C\x08'\ 53 | b'\x08\x1C\x2A\x08\x08'\ 54 | b'\x1E\x10\x10\x10\x10'\ 55 | b'\x0C\x1E\x0C\x1E\x0C'\ 56 | b'\x30\x38\x3E\x38\x30'\ 57 | b'\x06\x0E\x3E\x0E\x06'\ 58 | b'\x00\x00\x00\x00\x00'\ 59 | b'\x00\x00\x5F\x00\x00'\ 60 | b'\x00\x07\x00\x07\x00'\ 61 | b'\x14\x7F\x14\x7F\x14'\ 62 | b'\x24\x2A\x7F\x2A\x12'\ 63 | b'\x23\x13\x08\x64\x62'\ 64 | b'\x36\x49\x56\x20\x50'\ 65 | b'\x00\x08\x07\x03\x00'\ 66 | b'\x00\x1C\x22\x41\x00'\ 67 | b'\x00\x41\x22\x1C\x00'\ 68 | b'\x2A\x1C\x7F\x1C\x2A'\ 69 | b'\x08\x08\x3E\x08\x08'\ 70 | b'\x00\x80\x70\x30\x00'\ 71 | b'\x08\x08\x08\x08\x08'\ 72 | b'\x00\x00\x60\x60\x00'\ 73 | b'\x20\x10\x08\x04\x02'\ 74 | b'\x3E\x51\x49\x45\x3E'\ 75 | b'\x00\x42\x7F\x40\x00'\ 76 | b'\x72\x49\x49\x49\x46'\ 77 | b'\x21\x41\x49\x4D\x33'\ 78 | b'\x18\x14\x12\x7F\x10'\ 79 | b'\x27\x45\x45\x45\x39'\ 80 | b'\x3C\x4A\x49\x49\x31'\ 81 | b'\x41\x21\x11\x09\x07'\ 82 | b'\x36\x49\x49\x49\x36'\ 83 | b'\x46\x49\x49\x29\x1E'\ 84 | b'\x00\x00\x14\x00\x00'\ 85 | b'\x00\x40\x34\x00\x00'\ 86 | b'\x00\x08\x14\x22\x41'\ 87 | b'\x14\x14\x14\x14\x14'\ 88 | b'\x00\x41\x22\x14\x08'\ 89 | b'\x02\x01\x59\x09\x06'\ 90 | b'\x3E\x41\x5D\x59\x4E'\ 91 | b'\x7C\x12\x11\x12\x7C'\ 92 | b'\x7F\x49\x49\x49\x36'\ 93 | b'\x3E\x41\x41\x41\x22'\ 94 | b'\x7F\x41\x41\x41\x3E'\ 95 | b'\x7F\x49\x49\x49\x41'\ 96 | b'\x7F\x09\x09\x09\x01'\ 97 | b'\x3E\x41\x41\x51\x73'\ 98 | b'\x7F\x08\x08\x08\x7F'\ 99 | b'\x00\x41\x7F\x41\x00'\ 100 | b'\x20\x40\x41\x3F\x01'\ 101 | b'\x7F\x08\x14\x22\x41'\ 102 | b'\x7F\x40\x40\x40\x40'\ 103 | b'\x7F\x02\x1C\x02\x7F'\ 104 | b'\x7F\x04\x08\x10\x7F'\ 105 | b'\x3E\x41\x41\x41\x3E'\ 106 | b'\x7F\x09\x09\x09\x06'\ 107 | b'\x3E\x41\x51\x21\x5E'\ 108 | b'\x7F\x09\x19\x29\x46'\ 109 | b'\x26\x49\x49\x49\x32'\ 110 | b'\x03\x01\x7F\x01\x03'\ 111 | b'\x3F\x40\x40\x40\x3F'\ 112 | b'\x1F\x20\x40\x20\x1F'\ 113 | b'\x3F\x40\x38\x40\x3F'\ 114 | b'\x63\x14\x08\x14\x63'\ 115 | b'\x03\x04\x78\x04\x03'\ 116 | b'\x61\x59\x49\x4D\x43'\ 117 | b'\x00\x7F\x41\x41\x41'\ 118 | b'\x02\x04\x08\x10\x20'\ 119 | b'\x00\x41\x41\x41\x7F'\ 120 | b'\x04\x02\x01\x02\x04'\ 121 | b'\x40\x40\x40\x40\x40'\ 122 | b'\x00\x03\x07\x08\x00'\ 123 | b'\x20\x54\x54\x78\x40'\ 124 | b'\x7F\x28\x44\x44\x38'\ 125 | b'\x38\x44\x44\x44\x28'\ 126 | b'\x38\x44\x44\x28\x7F'\ 127 | b'\x38\x54\x54\x54\x18'\ 128 | b'\x00\x08\x7E\x09\x02'\ 129 | b'\x18\xA4\xA4\x9C\x78'\ 130 | b'\x7F\x08\x04\x04\x78'\ 131 | b'\x00\x44\x7D\x40\x00'\ 132 | b'\x20\x40\x40\x3D\x00'\ 133 | b'\x7F\x10\x28\x44\x00'\ 134 | b'\x00\x41\x7F\x40\x00'\ 135 | b'\x7C\x04\x78\x04\x78'\ 136 | b'\x7C\x08\x04\x04\x78'\ 137 | b'\x38\x44\x44\x44\x38'\ 138 | b'\xFC\x18\x24\x24\x18'\ 139 | b'\x18\x24\x24\x18\xFC'\ 140 | b'\x7C\x08\x04\x04\x08'\ 141 | b'\x48\x54\x54\x54\x24'\ 142 | b'\x04\x04\x3F\x44\x24'\ 143 | b'\x3C\x40\x40\x20\x7C'\ 144 | b'\x1C\x20\x40\x20\x1C'\ 145 | b'\x3C\x40\x30\x40\x3C'\ 146 | b'\x44\x28\x10\x28\x44'\ 147 | b'\x4C\x90\x90\x90\x7C'\ 148 | b'\x44\x64\x54\x4C\x44'\ 149 | b'\x00\x08\x36\x41\x00'\ 150 | b'\x00\x00\x77\x00\x00'\ 151 | b'\x00\x41\x36\x08\x00'\ 152 | b'\x02\x01\x02\x04\x02'\ 153 | b'\x3C\x26\x23\x26\x3C'\ 154 | b'\x1E\xA1\xA1\x61\x12'\ 155 | b'\x3A\x40\x40\x20\x7A'\ 156 | b'\x38\x54\x54\x55\x59'\ 157 | b'\x21\x55\x55\x79\x41'\ 158 | b'\x21\x54\x54\x78\x41'\ 159 | b'\x21\x55\x54\x78\x40'\ 160 | b'\x20\x54\x55\x79\x40'\ 161 | b'\x0C\x1E\x52\x72\x12'\ 162 | b'\x39\x55\x55\x55\x59'\ 163 | b'\x39\x54\x54\x54\x59'\ 164 | b'\x39\x55\x54\x54\x58'\ 165 | b'\x00\x00\x45\x7C\x41'\ 166 | b'\x00\x02\x45\x7D\x42'\ 167 | b'\x00\x01\x45\x7C\x40'\ 168 | b'\xF0\x29\x24\x29\xF0'\ 169 | b'\xF0\x28\x25\x28\xF0'\ 170 | b'\x7C\x54\x55\x45\x00'\ 171 | b'\x20\x54\x54\x7C\x54'\ 172 | b'\x7C\x0A\x09\x7F\x49'\ 173 | b'\x32\x49\x49\x49\x32'\ 174 | b'\x32\x48\x48\x48\x32'\ 175 | b'\x32\x4A\x48\x48\x30'\ 176 | b'\x3A\x41\x41\x21\x7A'\ 177 | b'\x3A\x42\x40\x20\x78'\ 178 | b'\x00\x9D\xA0\xA0\x7D'\ 179 | b'\x39\x44\x44\x44\x39'\ 180 | b'\x3D\x40\x40\x40\x3D'\ 181 | b'\x3C\x24\xFF\x24\x24'\ 182 | b'\x48\x7E\x49\x43\x66'\ 183 | b'\x2B\x2F\xFC\x2F\x2B'\ 184 | b'\xFF\x09\x29\xF6\x20'\ 185 | b'\xC0\x88\x7E\x09\x03'\ 186 | b'\x20\x54\x54\x79\x41'\ 187 | b'\x00\x00\x44\x7D\x41'\ 188 | b'\x30\x48\x48\x4A\x32'\ 189 | b'\x38\x40\x40\x22\x7A'\ 190 | b'\x00\x7A\x0A\x0A\x72'\ 191 | b'\x7D\x0D\x19\x31\x7D'\ 192 | b'\x26\x29\x29\x2F\x28'\ 193 | b'\x26\x29\x29\x29\x26'\ 194 | b'\x30\x48\x4D\x40\x20'\ 195 | b'\x38\x08\x08\x08\x08'\ 196 | b'\x08\x08\x08\x08\x38'\ 197 | b'\x2F\x10\xC8\xAC\xBA'\ 198 | b'\x2F\x10\x28\x34\xFA'\ 199 | b'\x00\x00\x7B\x00\x00'\ 200 | b'\x08\x14\x2A\x14\x22'\ 201 | b'\x22\x14\x2A\x14\x08'\ 202 | b'\xAA\x00\x55\x00\xAA'\ 203 | b'\xAA\x55\xAA\x55\xAA'\ 204 | b'\x00\x00\x00\xFF\x00'\ 205 | b'\x10\x10\x10\xFF\x00'\ 206 | b'\x14\x14\x14\xFF\x00'\ 207 | b'\x10\x10\xFF\x00\xFF'\ 208 | b'\x10\x10\xF0\x10\xF0'\ 209 | b'\x14\x14\x14\xFC\x00'\ 210 | b'\x14\x14\xF7\x00\xFF'\ 211 | b'\x00\x00\xFF\x00\xFF'\ 212 | b'\x14\x14\xF4\x04\xFC'\ 213 | b'\x14\x14\x17\x10\x1F'\ 214 | b'\x10\x10\x1F\x10\x1F'\ 215 | b'\x14\x14\x14\x1F\x00'\ 216 | b'\x10\x10\x10\xF0\x00'\ 217 | b'\x00\x00\x00\x1F\x10'\ 218 | b'\x10\x10\x10\x1F\x10'\ 219 | b'\x10\x10\x10\xF0\x10'\ 220 | b'\x00\x00\x00\xFF\x10'\ 221 | b'\x10\x10\x10\x10\x10'\ 222 | b'\x10\x10\x10\xFF\x10'\ 223 | b'\x00\x00\x00\xFF\x14'\ 224 | b'\x00\x00\xFF\x00\xFF'\ 225 | b'\x00\x00\x1F\x10\x17'\ 226 | b'\x00\x00\xFC\x04\xF4'\ 227 | b'\x14\x14\x17\x10\x17'\ 228 | b'\x14\x14\xF4\x04\xF4'\ 229 | b'\x00\x00\xFF\x00\xF7'\ 230 | b'\x14\x14\x14\x14\x14'\ 231 | b'\x14\x14\xF7\x00\xF7'\ 232 | b'\x14\x14\x14\x17\x14'\ 233 | b'\x10\x10\x1F\x10\x1F'\ 234 | b'\x14\x14\x14\xF4\x14'\ 235 | b'\x10\x10\xF0\x10\xF0'\ 236 | b'\x00\x00\x1F\x10\x1F'\ 237 | b'\x00\x00\x00\x1F\x14'\ 238 | b'\x00\x00\x00\xFC\x14'\ 239 | b'\x00\x00\xF0\x10\xF0'\ 240 | b'\x10\x10\xFF\x10\xFF'\ 241 | b'\x14\x14\x14\xFF\x14'\ 242 | b'\x10\x10\x10\x1F\x00'\ 243 | b'\x00\x00\x00\xF0\x10'\ 244 | b'\xFF\xFF\xFF\xFF\xFF'\ 245 | b'\xF0\xF0\xF0\xF0\xF0'\ 246 | b'\xFF\xFF\xFF\x00\x00'\ 247 | b'\x00\x00\x00\xFF\xFF'\ 248 | b'\x0F\x0F\x0F\x0F\x0F'\ 249 | b'\x38\x44\x44\x38\x44'\ 250 | b'\x7C\x2A\x2A\x3E\x14'\ 251 | b'\x7E\x02\x02\x06\x06'\ 252 | b'\x02\x7E\x02\x7E\x02'\ 253 | b'\x63\x55\x49\x41\x63'\ 254 | b'\x38\x44\x44\x3C\x04'\ 255 | b'\x40\x7E\x20\x1E\x20'\ 256 | b'\x06\x02\x7E\x02\x02'\ 257 | b'\x99\xA5\xE7\xA5\x99'\ 258 | b'\x1C\x2A\x49\x2A\x1C'\ 259 | b'\x4C\x72\x01\x72\x4C'\ 260 | b'\x30\x4A\x4D\x4D\x30'\ 261 | b'\x30\x48\x78\x48\x30'\ 262 | b'\xBC\x62\x5A\x46\x3D'\ 263 | b'\x3E\x49\x49\x49\x00'\ 264 | b'\x7E\x01\x01\x01\x7E'\ 265 | b'\x2A\x2A\x2A\x2A\x2A'\ 266 | b'\x44\x44\x5F\x44\x44'\ 267 | b'\x40\x51\x4A\x44\x40'\ 268 | b'\x40\x44\x4A\x51\x40'\ 269 | b'\x00\x00\xFF\x01\x03'\ 270 | b'\xE0\x80\xFF\x00\x00'\ 271 | b'\x08\x08\x6B\x6B\x08'\ 272 | b'\x36\x12\x36\x24\x36'\ 273 | b'\x06\x0F\x09\x0F\x06'\ 274 | b'\x00\x00\x18\x18\x00'\ 275 | b'\x00\x00\x10\x10\x00'\ 276 | b'\x30\x40\xFF\x01\x01'\ 277 | b'\x00\x1F\x01\x01\x1E'\ 278 | b'\x00\x19\x1D\x17\x12'\ 279 | b'\x00\x3C\x3C\x3C\x3C'\ 280 | b'\x00\x00\x00\x00\x00' 281 | 282 | _mvfont = memoryview(_font) 283 | 284 | def get_width(s): 285 | return len(s)*6 286 | 287 | def get_ch(ch): 288 | ordch = ord(ch) 289 | offset = ordch*5 290 | buf = bytearray(6) 291 | buf[0] = 0 292 | buf[1:]=_mvfont[offset:offset+5] 293 | return buf, 6 294 | 295 | -------------------------------------------------------------------------------- /utils/lcd/tt14.py: -------------------------------------------------------------------------------- 1 | # Code generated by font-to-py.py. 2 | # Font: CM Sans Serif 2012.ttf 3 | version = '0.2' 4 | 5 | def height(): 6 | return 15 7 | 8 | def max_width(): 9 | return 12 10 | 11 | def hmap(): 12 | return False 13 | 14 | def reverse(): 15 | return False 16 | 17 | def monospaced(): 18 | return False 19 | 20 | def min_ch(): 21 | return 32 22 | 23 | def max_ch(): 24 | return 126 25 | 26 | _font =\ 27 | b'\x08\x00\x08\x00\x0c\x00\x8c\x07\xcc\x07\xcc\x00\x7c\x00\x38\x00'\ 28 | b'\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\xfc\x06'\ 29 | b'\xfc\x06\x00\x00\x05\x00\x1c\x00\x3c\x00\x00\x00\x3c\x00\x00\x00'\ 30 | b'\x07\x00\x40\x03\xf0\x03\xf0\x03\x30\x03\xf0\x03\x30\x03\x30\x00'\ 31 | b'\x07\x00\x30\x00\x78\x03\x6c\x02\xfc\x07\xd8\x03\x90\x03\x00\x00'\ 32 | b'\x0b\x00\x18\x00\x3c\x00\x3c\x04\x3c\x03\xd8\x01\xf0\x03\xd8\x07'\ 33 | b'\xc4\x06\xc0\x07\x80\x03\x00\x00\x09\x00\x80\x03\xf8\x07\x7c\x06'\ 34 | b'\x4c\x06\xfc\x06\x98\x07\xc0\x07\xc0\x05\x00\x04\x02\x00\x1c\x00'\ 35 | b'\x3c\x00\x04\x00\xf0\x03\xfc\x0f\x0e\x1c\x02\x10\x05\x00\x02\x10'\ 36 | b'\x0e\x1c\xfc\x0f\xf0\x03\x00\x00\x05\x00\x0c\x00\x3c\x00\x18\x00'\ 37 | b'\x2c\x00\x0c\x00\x07\x00\xc0\x00\xc0\x00\xf0\x03\xf0\x03\xc0\x00'\ 38 | b'\xc0\x00\x00\x00\x03\x00\x00\x16\x00\x0e\x00\x00\x04\x00\xc0\x00'\ 39 | b'\xc0\x00\xc0\x00\xc0\x00\x03\x00\x00\x06\x00\x06\x00\x00\x04\x00'\ 40 | b'\x00\x06\xe0\x07\xfc\x00\x0e\x00\x08\x00\xf0\x01\xf8\x03\x1c\x07'\ 41 | b'\x0c\x06\x1c\x07\xf8\x03\xf0\x01\x00\x00\x04\x00\x18\x00\xfc\x07'\ 42 | b'\xfc\x07\x00\x00\x08\x00\x18\x04\x1c\x07\x8c\x06\x8c\x06\x4c\x06'\ 43 | b'\x7c\x06\x38\x06\x00\x00\x07\x00\x08\x02\x0c\x06\x0c\x06\x6c\x06'\ 44 | b'\x6c\x06\xfc\x07\xd8\x03\x07\x00\x80\x01\xe0\x01\xb0\x01\x9c\x01'\ 45 | b'\xfc\x07\xfc\x07\x80\x01\x07\x00\x00\x02\xfc\x06\x6c\x06\x6c\x06'\ 46 | b'\x6c\x06\xcc\x03\xc0\x03\x08\x00\xf0\x01\xf8\x03\x6c\x06\x6c\x06'\ 47 | b'\x6c\x06\xe8\x07\xc0\x03\x00\x00\x06\x00\x0c\x00\x0c\x07\xcc\x07'\ 48 | b'\xfc\x00\x1c\x00\x0c\x00\x07\x00\x98\x03\xfc\x07\x6c\x06\x6c\x06'\ 49 | b'\x6c\x06\xfc\x07\x98\x03\x08\x00\x78\x02\xfc\x06\xcc\x06\xcc\x06'\ 50 | b'\xcc\x06\xf8\x03\xf0\x01\x00\x00\x03\x00\x30\x06\x30\x06\x00\x00'\ 51 | b'\x03\x00\x30\x16\x30\x0e\x00\x00\x06\x00\xc0\x01\xc0\x01\x60\x03'\ 52 | b'\x60\x03\x30\x06\x00\x00\x06\x00\x60\x03\x60\x03\x60\x03\x60\x03'\ 53 | b'\x60\x03\x60\x03\x06\x00\x30\x06\x60\x03\x60\x03\x40\x01\xc0\x01'\ 54 | b'\x00\x00\x08\x00\x08\x00\x0c\x00\x8c\x07\xcc\x07\xcc\x00\x7c\x00'\ 55 | b'\x38\x00\x00\x00\x0b\x00\xe0\x00\xf8\x01\x18\x03\xec\x07\xbc\x07'\ 56 | b'\xfc\x06\xfc\x07\x9c\x03\xf8\x01\xf0\x00\x00\x00\x08\x00\x00\x06'\ 57 | b'\x80\x07\xf0\x01\x9c\x01\x8c\x01\xf8\x01\xc0\x07\x00\x07\x08\x00'\ 58 | b'\xfc\x07\xfc\x07\x6c\x06\x6c\x06\x6c\x06\xfc\x07\xd8\x03\x00\x00'\ 59 | b'\x09\x00\xe0\x00\xf8\x03\x18\x03\x0c\x06\x0c\x06\x0c\x06\x1c\x07'\ 60 | b'\x18\x03\x20\x01\x09\x00\xfc\x07\xfc\x07\x0c\x06\x0c\x06\x1c\x07'\ 61 | b'\xf8\x03\xf0\x01\x00\x00\x00\x00\x08\x00\xfc\x07\xfc\x07\x6c\x06'\ 62 | b'\x6c\x06\x6c\x06\x6c\x06\x0c\x06\x00\x00\x07\x00\xfc\x07\xfc\x07'\ 63 | b'\xcc\x00\xcc\x00\xcc\x00\xcc\x00\x0c\x00\x0a\x00\xe0\x01\xf8\x03'\ 64 | b'\x18\x07\x0c\x06\x0c\x06\xcc\x06\xdc\x02\xd8\x07\xe0\x07\x00\x00'\ 65 | b'\x09\x00\xfc\x07\xfc\x07\x60\x00\x60\x00\x60\x00\xfc\x07\xfc\x07'\ 66 | b'\x00\x00\x00\x00\x03\x00\xfc\x07\xfc\x07\x00\x00\x07\x00\x80\x03'\ 67 | b'\x80\x07\x00\x06\x00\x06\xfc\x07\xfc\x03\x00\x00\x08\x00\xfc\x07'\ 68 | b'\xfc\x07\x60\x00\xf0\x00\xd8\x01\x0c\x07\x04\x06\x04\x04\x07\x00'\ 69 | b'\xfc\x07\xfc\x07\x00\x06\x00\x06\x00\x06\x00\x06\x00\x00\x0b\x00'\ 70 | b'\xfc\x07\xfc\x07\x3c\x00\xe0\x03\x00\x06\xe0\x03\x3c\x00\xfc\x07'\ 71 | b'\xfc\x07\x00\x00\x00\x00\x09\x00\xfc\x07\xfc\x07\x38\x00\xe0\x00'\ 72 | b'\x80\x03\xfc\x07\xfc\x07\x00\x00\x00\x00\x0a\x00\xf0\x01\xf8\x03'\ 73 | b'\x1c\x07\x0c\x06\x0c\x06\x0c\x06\x1c\x07\xf8\x03\xf0\x01\x00\x00'\ 74 | b'\x09\x00\xfc\x07\xfc\x07\xcc\x00\xcc\x00\xcc\x00\xfc\x00\x78\x00'\ 75 | b'\x00\x00\x00\x00\x0a\x00\xf0\x01\xf8\x03\x1c\x07\x0c\x06\x8c\x06'\ 76 | b'\x8c\x07\x1c\x07\xf8\x07\xf0\x05\x00\x00\x09\x00\xfc\x07\xfc\x07'\ 77 | b'\xcc\x00\xcc\x00\xcc\x03\x7c\x07\x78\x04\x00\x00\x00\x00\x09\x00'\ 78 | b'\x38\x01\x3c\x03\x4c\x06\x4c\x06\x4c\x06\x4c\x06\x98\x07\x90\x03'\ 79 | b'\x00\x00\x07\x00\x0c\x00\x0c\x00\xfc\x07\xfc\x07\x0c\x00\x0c\x00'\ 80 | b'\x00\x00\x09\x00\xfc\x03\xfc\x07\x00\x06\x00\x06\x00\x06\xfc\x07'\ 81 | b'\xfc\x01\x00\x00\x00\x00\x08\x00\x0c\x00\x7c\x00\xe0\x03\x00\x07'\ 82 | b'\x80\x07\xf8\x01\x3c\x00\x04\x00\x0b\x00\x0c\x00\xfc\x00\xe0\x07'\ 83 | b'\x80\x07\xf8\x03\x1c\x00\xfc\x00\xc0\x07\x80\x07\xfc\x01\x1c\x00'\ 84 | b'\x08\x00\x04\x04\x0c\x07\xb8\x03\xf0\x00\xf0\x01\x9c\x07\x0c\x06'\ 85 | b'\x04\x04\x08\x00\x04\x00\x1c\x00\x78\x00\xe0\x07\xe0\x07\x78\x00'\ 86 | b'\x1c\x00\x04\x00\x07\x00\x0c\x06\x0c\x07\x8c\x07\xcc\x06\x2c\x06'\ 87 | b'\x1c\x06\x0c\x06\x04\x00\xff\x1f\xff\x1f\x03\x18\x00\x00\x04\x00'\ 88 | b'\x0e\x00\xfe\x00\xe0\x07\x00\x07\x04\x00\x03\x18\xff\x1f\xff\x1f'\ 89 | b'\x00\x00\x07\x00\x80\x00\xe0\x00\x7c\x00\x0c\x00\x7c\x00\xe0\x00'\ 90 | b'\x80\x00\x0a\x00\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18'\ 91 | b'\x00\x18\x00\x18\x00\x18\x00\x18\x05\x00\x04\x00\x0c\x00\x08\x00'\ 92 | b'\x00\x00\x00\x00\x08\x00\x00\x03\xe0\x07\xb0\x06\xb0\x06\xb0\x02'\ 93 | b'\xf0\x07\xe0\x07\x00\x00\x09\x00\xfe\x07\xfe\x07\x30\x02\x30\x06'\ 94 | b'\x30\x06\xe0\x07\xc0\x01\x00\x00\x00\x00\x07\x00\xc0\x01\xe0\x03'\ 95 | b'\x30\x06\x30\x06\x30\x06\x60\x03\x40\x01\x08\x00\xc0\x01\xf0\x07'\ 96 | b'\x30\x06\x30\x06\x30\x06\xfe\x07\xfe\x07\x00\x00\x07\x00\xc0\x01'\ 97 | b'\xe0\x03\xb0\x07\xb0\x07\xb0\x07\xe0\x03\xc0\x01\x04\x00\x20\x00'\ 98 | b'\xfc\x07\xfe\x07\x00\x00\x08\x00\xc0\x11\xf0\x1f\x30\x36\x30\x36'\ 99 | b'\x20\x36\xf0\x1f\xf0\x0f\x00\x00\x08\x00\xfe\x07\xfe\x07\x30\x00'\ 100 | b'\x30\x00\xf0\x07\xe0\x07\x00\x00\x00\x00\x03\x00\xfc\x07\xfc\x07'\ 101 | b'\x00\x00\x03\x00\xec\x7f\xec\x3f\x00\x00\x07\x00\xfe\x07\xfe\x07'\ 102 | b'\xc0\x00\xe0\x01\xb0\x07\x10\x06\x10\x04\x03\x00\xfe\x07\xfe\x07'\ 103 | b'\x00\x00\x0c\x00\xf0\x07\xf0\x07\x30\x00\x30\x00\xf0\x07\xe0\x07'\ 104 | b'\x30\x00\x30\x00\xf0\x07\xe0\x07\x00\x00\x00\x00\x08\x00\xf0\x07'\ 105 | b'\xf0\x07\x30\x00\x30\x00\xf0\x07\xe0\x07\x00\x00\x00\x00\x08\x00'\ 106 | b'\xc0\x01\xe0\x03\x30\x06\x30\x06\x30\x06\xe0\x03\xc0\x01\x00\x00'\ 107 | b'\x09\x00\xf0\x3f\xf0\x3f\x30\x06\x30\x06\x30\x06\xf0\x07\xc0\x01'\ 108 | b'\x00\x00\x00\x00\x08\x00\xc0\x01\xf0\x07\x30\x06\x30\x06\x30\x06'\ 109 | b'\xf0\x3f\xf0\x3f\x00\x00\x04\x00\xf0\x07\xf0\x07\x30\x00\x30\x00'\ 110 | b'\x08\x00\x60\x00\x70\x03\xb0\x07\xf0\x06\xb0\x06\xa0\x07\x00\x03'\ 111 | b'\x00\x00\x04\x00\x30\x00\xfe\x03\xfe\x07\x00\x00\x08\x00\xf0\x03'\ 112 | b'\xf0\x07\x00\x06\x00\x06\xf0\x07\xf0\x07\x00\x00\x00\x00\x07\x00'\ 113 | b'\x30\x00\xf0\x00\x80\x07\x00\x06\xc0\x03\xf0\x00\x10\x00\x0a\x00'\ 114 | b'\x30\x00\xf0\x01\x80\x07\xc0\x07\xf0\x00\xf0\x00\x80\x07\x80\x07'\ 115 | b'\xf0\x01\x30\x00\x07\x00\x10\x04\x30\x07\xe0\x03\xc0\x01\x70\x07'\ 116 | b'\x10\x06\x00\x04\x07\x00\x30\x00\xf0\x30\xc0\x3f\x00\x1f\xe0\x03'\ 117 | b'\x70\x00\x10\x00\x06\x00\x30\x06\x30\x07\xb0\x07\xf0\x06\x70\x06'\ 118 | b'\x30\x06\x05\x00\x40\x00\xbe\x0f\xbe\x0f\x00\x00\x00\x00\x04\x00'\ 119 | b'\xfe\x07\xfe\x07\x00\x00\x00\x00\x04\x00\xbe\x0f\xbe\x0f\x40\x00'\ 120 | b'\x00\x00\x05\x00\x04\x00\x04\x00\x04\x00\x06\x00\x04\x00' 121 | 122 | _index =\ 123 | b'\x00\x00\x12\x00\x1c\x00\x24\x00\x30\x00\x40\x00\x50\x00\x68\x00'\ 124 | b'\x7c\x00\x82\x00\x8c\x00\x98\x00\xa4\x00\xb4\x00\xbc\x00\xc6\x00'\ 125 | b'\xce\x00\xd8\x00\xea\x00\xf4\x00\x06\x01\x16\x01\x26\x01\x36\x01'\ 126 | b'\x48\x01\x56\x01\x66\x01\x78\x01\x80\x01\x88\x01\x96\x01\xa4\x01'\ 127 | b'\xb2\x01\xc4\x01\xdc\x01\xee\x01\x00\x02\x14\x02\x28\x02\x3a\x02'\ 128 | b'\x4a\x02\x60\x02\x74\x02\x7c\x02\x8c\x02\x9e\x02\xae\x02\xc6\x02'\ 129 | b'\xda\x02\xf0\x02\x04\x03\x1a\x03\x2e\x03\x42\x03\x52\x03\x66\x03'\ 130 | b'\x78\x03\x90\x03\xa2\x03\xb4\x03\xc4\x03\xce\x03\xd8\x03\xe2\x03'\ 131 | b'\xf2\x03\x08\x04\x14\x04\x26\x04\x3a\x04\x4a\x04\x5c\x04\x6c\x04'\ 132 | b'\x76\x04\x88\x04\x9a\x04\xa2\x04\xaa\x04\xba\x04\xc2\x04\xdc\x04'\ 133 | b'\xee\x04\x00\x05\x14\x05\x26\x05\x30\x05\x42\x05\x4c\x05\x5e\x05'\ 134 | b'\x6e\x05\x84\x05\x94\x05\xa4\x05\xb2\x05\xbe\x05\xc8\x05\xd2\x05'\ 135 | b'\xde\x05' 136 | 137 | _mvfont = memoryview(_font) 138 | 139 | def _chr_addr(ordch): 140 | offset = 2 * (ordch - 32) 141 | return int.from_bytes(_index[offset:offset + 2], 'little') 142 | 143 | def get_width(s): 144 | width = 0 145 | for ch in s: 146 | ordch = ord(ch) 147 | ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 148 | offset = _chr_addr(ordch) 149 | width += int.from_bytes(_font[offset:offset + 2], 'little') 150 | return width 151 | 152 | def get_ch(ch): 153 | ordch = ord(ch) 154 | ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 155 | offset = _chr_addr(ordch) 156 | width = int.from_bytes(_font[offset:offset + 2], 'little') 157 | next_offs = _chr_addr(ordch +1) 158 | return _mvfont[offset + 2:next_offs], width 159 | 160 | -------------------------------------------------------------------------------- /utils/lcd/ili934xnew.py: -------------------------------------------------------------------------------- 1 | # This is an adapted version of the ILI934X driver as below. 2 | # It works with multiple fonts and also works with the esp32 H/W SPI implementation 3 | # Also includes a word wrap print function 4 | # Proportional fonts are generated by Peter Hinch's Font-to-py 5 | # MIT License; Copyright (c) 2017 Jeffrey N. Magee 6 | 7 | # This file is part of MicroPython ILI934X driver 8 | # Copyright (c) 2016 - 2017 Radomir Dopieralski, Mika Tuupola 9 | # 10 | # Licensed under the MIT license: 11 | # http://www.opensource.org/licenses/mit-license.php 12 | # 13 | # Project home: 14 | # https://github.com/tuupola/micropython-ili934x 15 | 16 | import time 17 | import ustruct 18 | import framebuf 19 | from .fonts import GLCDFONT 20 | from micropython import const 21 | 22 | _RDDSDR = const(0x0f) # Read Display Self-Diagnostic Result 23 | _SLPOUT = const(0x11) # Sleep Out 24 | _GAMSET = const(0x26) # Gamma Set 25 | _DISPOFF = const(0x28) # Display Off 26 | _DISPON = const(0x29) # Display On 27 | _CASET = const(0x2a) # Column Address Set 28 | _PASET = const(0x2b) # Page Address Set 29 | _RAMWR = const(0x2c) # Memory Write 30 | _RAMRD = const(0x2e) # Memory Read 31 | _MADCTL = const(0x36) # Memory Access Control 32 | _VSCRSADD = const(0x37) # Vertical Scrolling Start Address 33 | _PIXSET = const(0x3a) # Pixel Format Set 34 | _PWCTRLA = const(0xcb) # Power Control A 35 | _PWCRTLB = const(0xcf) # Power Control B 36 | _DTCTRLA = const(0xe8) # Driver Timing Control A 37 | _DTCTRLB = const(0xea) # Driver Timing Control B 38 | _PWRONCTRL = const(0xed) # Power on Sequence Control 39 | _PRCTRL = const(0xf7) # Pump Ratio Control 40 | _PWCTRL1 = const(0xc0) # Power Control 1 41 | _PWCTRL2 = const(0xc1) # Power Control 2 42 | _VMCTRL1 = const(0xc5) # VCOM Control 1 43 | _VMCTRL2 = const(0xc7) # VCOM Control 2 44 | _FRMCTR1 = const(0xb1) # Frame Rate Control 1 45 | _DISCTRL = const(0xb6) # Display Function Control 46 | _INTFACE = const(0xf6) # Interface 47 | _ENA3G = const(0xf2) # Enable 3G 48 | _PGAMCTRL = const(0xe0) # Positive Gamma Control 49 | _NGAMCTRL = const(0xe1) # Negative Gamma Control 50 | 51 | _CHUNK = const(1024) #maximum number of pixels per spi write 52 | 53 | def color565(r, g, b): 54 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 55 | 56 | class ILI9341: 57 | 58 | width = 320 59 | height = 240 60 | 61 | def __init__(self, spi, cs, dc, rst=None): 62 | self.spi = spi 63 | self.cs = cs 64 | self.dc = dc 65 | # self.rst = rst 66 | self.cs.init(self.cs.OUT, value=1) 67 | self.dc.init(self.dc.OUT, value=0) 68 | # self.rst.init(self.rst.OUT, value=0) 69 | # self.reset() 70 | self.init() 71 | self._scroll = 0 72 | self._buf = bytearray(_CHUNK * 2) 73 | self._colormap = bytearray(b'\x00\x00\xFF\xFF') #default white foregraound, black background 74 | self._x = 0 75 | self._y = 0 76 | self._font = GLCDFONT 77 | self.scrolling = False 78 | 79 | def set_color(self,fg,bg): 80 | self._colormap[0] = bg>>8 81 | self._colormap[1] = bg & 255 82 | self._colormap[2] = fg>>8 83 | self._colormap[3] = fg & 255 84 | 85 | def set_pos(self,x,y): 86 | self._x = x 87 | self._y = y 88 | 89 | def reset_scroll(self): 90 | self.scrolling = False 91 | self._scroll = 0 92 | self.scroll(0) 93 | 94 | def set_font(self, font): 95 | self._font = font 96 | 97 | def init(self): 98 | for command, data in ( 99 | (_RDDSDR, b"\x03\x80\x02"), 100 | (_PWCRTLB, b"\x00\xcf\x30"), 101 | (_PWRONCTRL, b"\x64\x03\x12\x81"), 102 | (_DTCTRLA, b"\x85\x00\x78"), 103 | (_PWCTRLA, b"\x39\x2c\x00\x34\x02"), 104 | (_PRCTRL, b"\x20"), 105 | (_DTCTRLB, b"\x00\x00"), 106 | (_PWCTRL1, b"\x1b"), 107 | (_PWCTRL2, b"\x12"), 108 | (_VMCTRL1, b"\x3e\x3c"), 109 | (_VMCTRL2, b"\x91"), 110 | (_MADCTL, b"\xa8"), 111 | (_PIXSET, b"\x55"), 112 | (_FRMCTR1, b"\x00\x1b"), 113 | (_DISCTRL, b"\x0a\xa2\x27"), 114 | (_INTFACE, b"\x01\x30"), 115 | (_ENA3G, b"\x00"), 116 | (_GAMSET, b"\x01"), 117 | (_PGAMCTRL, b"\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00"), 118 | (_NGAMCTRL, b"\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f")): 119 | self._write(command, data) 120 | self._write(_SLPOUT) 121 | time.sleep_ms(120) 122 | self._write(_DISPON) 123 | 124 | # def reset(self): 125 | # self.rst(0) 126 | # time.sleep_ms(50) 127 | # self.rst(1) 128 | # time.sleep_ms(50) 129 | 130 | def _write(self, command, data=None): 131 | self.dc(0) 132 | self.cs(0) 133 | self.spi.write(bytearray([command])) 134 | self.cs(1) 135 | if data is not None: 136 | self._data(data) 137 | 138 | def _data(self, data): 139 | self.dc(1) 140 | self.cs(0) 141 | self.spi.write(data) 142 | self.cs(1) 143 | 144 | def _writeblock(self, x0, y0, x1, y1, data=None): 145 | self._write(_CASET, ustruct.pack(">HH", x0, x1)) 146 | self._write(_PASET, ustruct.pack(">HH", y0, y1)) 147 | self._write(_RAMWR, data) 148 | 149 | def _readblock(self, x0, y0, x1, y1): 150 | self._write(_CASET, ustruct.pack(">HH", x0, x1)) 151 | self._write(_PASET, ustruct.pack(">HH", y0, y1)) 152 | if data is None: 153 | return self._read(_RAMRD, (x1 - x0 + 1) * (y1 - y0 + 1) * 3) 154 | 155 | def _read(self, command, count): 156 | self.dc(0) 157 | self.cs(0) 158 | self.spi.write(bytearray([command])) 159 | data = self.spi.read(count) 160 | self.cs(1) 161 | return data 162 | 163 | def pixel(self, x, y, color=None): 164 | if color is None: 165 | r, b, g = self._readblock(x, y, x, y) 166 | return color565(r, g, b) 167 | if not 0 <= x < self.width or not 0 <= y < self.height: 168 | return 169 | self._writeblock(x, y, x, y, ustruct.pack(">H", color)) 170 | 171 | def fill_rectangle(self, x, y, w, h, color=None): 172 | x = min(self.width - 1, max(0, x)) 173 | y = min(self.height - 1, max(0, y)) 174 | w = min(self.width - x, max(1, w)) 175 | h = min(self.height - y, max(1, h)) 176 | if color: 177 | color = ustruct.pack(">H", color) 178 | else: 179 | color = self._colormap[0:2] #background 180 | for i in range(_CHUNK): 181 | self._buf[2*i]=color[0]; self._buf[2*i+1]=color[1] 182 | chunks, rest = divmod(w * h, _CHUNK) 183 | self._writeblock(x, y, x + w - 1, y + h - 1, None) 184 | if chunks: 185 | for count in range(chunks): 186 | self._data(self._buf) 187 | if rest != 0: 188 | mv = memoryview(self._buf) 189 | self._data(mv[:rest*2]) 190 | 191 | def fill(self, color): 192 | self.fill_rectangle(0, 0, self.width, self.height, color) 193 | 194 | def erase(self): 195 | self.fill_rectangle(0, 0, self.width, self.height) 196 | 197 | def blit(self, bitbuff, x, y, w, h): 198 | x = min(self.width - 1, max(0, x)) 199 | y = min(self.height - 1, max(0, y)) 200 | w = min(self.width - x, max(1, w)) 201 | h = min(self.height - y, max(1, h)) 202 | chunks, rest = divmod(w * h, _CHUNK) 203 | self._writeblock(x, y, x + w - 1, y + h - 1, None) 204 | written = 0 205 | for iy in range(h): 206 | for ix in range(w): 207 | index = ix+iy*w - written 208 | if index >=_CHUNK: 209 | self._data(self._buf) 210 | written += _CHUNK 211 | index -= _CHUNK 212 | c = bitbuff.pixel(ix,iy) 213 | self._buf[index*2] = self._colormap[c*2] 214 | self._buf[index*2+1] = self._colormap[c*2+1] 215 | rest = w*h - written 216 | if rest != 0: 217 | mv = memoryview(self._buf) 218 | self._data(mv[:rest*2]) 219 | 220 | def chars(self, str, x, y): 221 | str_w = self._font.get_width(str) 222 | div, rem = divmod(self._font.height(),8) 223 | nbytes = div+1 if rem else div 224 | buf = bytearray(str_w * nbytes) 225 | pos = 0 226 | for ch in str: 227 | glyph, char_w = self._font.get_ch(ch) 228 | for row in range(nbytes): 229 | index = row*str_w + pos 230 | for i in range(char_w): 231 | buf[index+i] = glyph[nbytes*i+row] 232 | pos += char_w 233 | fb = framebuf.FrameBuffer(buf,str_w, self._font.height(), framebuf.MONO_VLSB) 234 | self.blit(fb,x,y,str_w,self._font.height()) 235 | return x+str_w 236 | 237 | def scroll(self, dy): 238 | self._scroll = (self._scroll + dy) % self.height 239 | self._write(_VSCRSADD, ustruct.pack(">H", self._scroll)) 240 | 241 | def next_line(self, cury, char_h): 242 | global scrolling 243 | if not self.scrolling: 244 | res = cury + char_h 245 | self.scrolling = (res >= self.height) 246 | if self.scrolling: 247 | self.scroll(char_h) 248 | res = (self.height - char_h + self._scroll)%self.height 249 | self.fill_rectangle(0, res, self.width, self._font.height()) 250 | return res 251 | 252 | def write(self, text): #does character wrap, compatible with stream output 253 | curx = self._x; cury = self._y 254 | char_h = self._font.height() 255 | width = 0 256 | written = 0 257 | for pos, ch in enumerate(text): 258 | if ch == '\n': 259 | if pos>0: 260 | self.chars(text[written:pos],curx,cury) 261 | curx = 0; written = pos+1; width = 0 262 | cury = self.next_line(cury,char_h) 263 | else: 264 | char_w = self._font.get_width(ch) 265 | if curx + width + char_w >= self.width: 266 | self.chars(text[written:pos], curx,cury) 267 | curx = 0 ; written = pos; width = char_h 268 | cury = self.next_line(cury,char_h) 269 | else: 270 | width += char_w 271 | if written= self.width: 285 | curx = self._x; cury = self.next_line(cury,char_h) 286 | while self._font.get_width(word) > self.width: 287 | self.chars(word[:self.width//char_w],curx,cury) 288 | word = word[self.width//char_w:] 289 | cury = self.next_line(cury,char_h) 290 | if len(word)>0: 291 | curx = self.chars(word+' ', curx,cury) 292 | curx = self._x; cury = self.next_line(cury,char_h) 293 | self._y = cury 294 | -------------------------------------------------------------------------------- /pong.py: -------------------------------------------------------------------------------- 1 | # pongs.py 2 | # 3 | # Use common game module "gameESP.py" for ESP8266 or ESP32 4 | # by Billy Cheung 2019 10 26 5 | # 6 | import gc 7 | import sys 8 | gc.collect() 9 | print (gc.mem_free()) 10 | import utime 11 | from utime import sleep_ms 12 | import network 13 | from math import sqrt 14 | # all dislplay, buttons, paddle, sound logics are in gameESP.mpy module 15 | from gameESP import * 16 | g=gameESP() 17 | 18 | scores = [0,0] 19 | 20 | maxScore = 15 21 | gameOver = False 22 | exitGame = False 23 | 24 | 25 | class bat(Rect): 26 | def __init__(self, velocity, up_key, down_key, *args, **kwargs): 27 | self.velocity = velocity 28 | self.up_key = up_key 29 | self.down_key = down_key 30 | super().__init__(*args, **kwargs) 31 | 32 | def move_bat(self, board_height, bat_HEIGHT, balls): 33 | g.getBtn() 34 | 35 | if self.up_key == 0 : # use AI 36 | ballXdiff = 40 37 | ballY = -1 38 | for ball in balls : 39 | if abs(ball.x - self.x) < ballXdiff : 40 | ballXdiff = abs(ball.x - self.x) 41 | ballY = ball.y 42 | if ballY >= 0 : 43 | self.y = max(min(ballY - pong.bat_HEIGHT//2 + g.random(0,pong.bat_HEIGHT//2+1), board_height-pong.bat_HEIGHT),0) 44 | 45 | elif self.up_key == -1 : # use Paddle 46 | self.y = int (g.getPaddle() / (1024 / (board_height-pong.bat_HEIGHT))) 47 | self.y = int (g.getPaddle() / (1024 / (board_height-pong.bat_HEIGHT))) 48 | 49 | elif self.up_key == -2 : # use Paddle 2 50 | self.y = int (g.getPaddle2() / (1024 / (board_height-pong.bat_HEIGHT))) 51 | else : 52 | if g.pressed(self.up_key): 53 | self.y = max(self.y - self.velocity,0) 54 | if g.pressed(self.down_key): 55 | self.y = min(self.y + self.velocity, board_height-pong.bat_HEIGHT) 56 | 57 | class Ball(Rect): 58 | def __init__(self, velocity, *args, **kwargs): 59 | self.velocity = velocity 60 | self.angle = 0 61 | super().__init__(*args, **kwargs) 62 | 63 | def move_ball(self): 64 | self.x += self.velocity 65 | self.y += self.angle 66 | 67 | 68 | class Pong: 69 | HEIGHT = 64 70 | WIDTH = 128 71 | 72 | bat_WIDTH = 2 73 | bat_HEIGHT = 15 74 | bat_VELOCITY = 3 75 | 76 | BALL_WIDTH = 2 77 | BALL_VELOCITY = 2 78 | BALL_ANGLE = 0 79 | 80 | COLOUR = 1 81 | scores = [0,0] 82 | maxScore = 15 83 | maxballs = 2 84 | ballschance = 50 85 | 86 | def init (self, onePlayer, demo, usePaddle): 87 | # Setup the screen 88 | global scores 89 | scores = [0,0] 90 | # Create the player objects. 91 | self.bats = [] 92 | self.balls = [] 93 | 94 | if demo : 95 | self.bats.append(bat( # The left bat, AI 96 | self.bat_VELOCITY, 97 | 0, 98 | 0, 99 | 0, 100 | int(self.HEIGHT / 2 - self.bat_HEIGHT / 2), 101 | self.bat_WIDTH, 102 | self.bat_HEIGHT)) 103 | elif usePaddle : 104 | self.bats.append(bat( # The left bat, use Paddle 105 | self.bat_VELOCITY, 106 | -1, 107 | -1, 108 | 0, 109 | int(self.HEIGHT / 2 - self.bat_HEIGHT / 2), 110 | self.bat_WIDTH, 111 | self.bat_HEIGHT)) 112 | else : 113 | 114 | self.bats.append(bat( # The left bat, button controlled 115 | self.bat_VELOCITY, 116 | g.btnU, 117 | g.btnD, 118 | 0, 119 | int(self.HEIGHT / 2 - self.bat_HEIGHT / 2), 120 | self.bat_WIDTH, 121 | self.bat_HEIGHT)) 122 | 123 | # set up control method for left Bat 124 | if demo or onePlayer: 125 | self.bats.append(bat( # The right bat, AI 126 | self.bat_VELOCITY, 127 | 0, 128 | 0, 129 | self.WIDTH - self.bat_WIDTH-1, 130 | int(self.HEIGHT / 2 - self.bat_HEIGHT / 2), 131 | self.bat_WIDTH, 132 | self.bat_HEIGHT 133 | )) 134 | elif usePaddle and g.paddle2 : # only use paddle2 if its present on the boards 135 | self.bats.append(bat( # The right bat, use Paddle 136 | self.bat_VELOCITY, 137 | -2, 138 | -2, 139 | self.WIDTH - self.bat_WIDTH-1, 140 | int(self.HEIGHT / 2 - self.bat_HEIGHT / 2), 141 | self.bat_WIDTH, 142 | self.bat_HEIGHT)) 143 | else : # use buttons for the right bat 144 | self.bats.append(bat( # The right bat, button controlled 145 | self.bat_VELOCITY, 146 | g.btnB, 147 | g.btnA, 148 | self.WIDTH - self.bat_WIDTH-1, 149 | int(self.HEIGHT / 2 - self.bat_HEIGHT / 2), 150 | self.bat_WIDTH, 151 | self.bat_HEIGHT 152 | )) 153 | 154 | self.balls.append(Ball( 155 | self.BALL_VELOCITY, 156 | int(self.WIDTH / 2 - self.BALL_WIDTH / 2), 157 | int(self.HEIGHT / 2 - self.BALL_WIDTH / 2), 158 | self.BALL_WIDTH, 159 | self.BALL_WIDTH)) 160 | 161 | 162 | def score(self, player, ball): 163 | global gameOver 164 | global scores 165 | scores[player] += 1 166 | g.playTone ('g4', 100) 167 | 168 | if len (self.balls) > 1 : 169 | self.balls.remove(ball) 170 | else : 171 | ball.velocity = - ball.velocity 172 | ball.angle = g.random(0,3) - 2 173 | ball.x = int(self.WIDTH / 2 - self.BALL_WIDTH / 2) 174 | ball.y = int(self.HEIGHT / 2 - self.BALL_WIDTH / 2) 175 | 176 | 177 | if scores[player] >= maxScore : 178 | gameOver = True 179 | 180 | def check_ball_hits_wall(self): 181 | for ball in self.balls: 182 | 183 | if ball.x < 0: 184 | self.score(1, ball) 185 | 186 | 187 | if ball.x > self.WIDTH : 188 | self.score(0, ball) 189 | 190 | if ball.y > self.HEIGHT - self.BALL_WIDTH or ball.y < 0: 191 | ball.angle = -ball.angle 192 | 193 | 194 | def check_ball_hits_bat(self): 195 | for ball in self.balls: 196 | for bat in self.bats: 197 | if ball.colliderect(bat): 198 | ball.velocity = -ball.velocity 199 | ball.angle = g.random (0,3) - 2 200 | g.playTone ('c6', 10) 201 | break 202 | 203 | def game_loop(self): 204 | global gameOver, exitGame, scores 205 | demoOn = False 206 | exitGame = False 207 | while not exitGame: 208 | if demoOn : 209 | playsers = 0 210 | demo = True 211 | else : 212 | players = 1 213 | demo = False 214 | 215 | onePlayer = True 216 | usePaddle = False 217 | gameOver = False 218 | 219 | #menu screen 220 | while True: 221 | g.display.fill(0) 222 | g.display.text('Pong', 0, 0, 1) 223 | g.display.rect(90,0, g.max_vol*4+2,6,1) 224 | g.display.fill_rect(91,1, g.vol * 4,4,1) 225 | g.display.text('A Start B+L Quit', 0, 10, 1) 226 | if usePaddle : 227 | g.display.text('U Paddle', 0,20, 1) 228 | else : 229 | g.display.text('U Button', 0,20, 1) 230 | if players == 0 : 231 | g.display.text('D AI-Player', 0,30, 1) 232 | elif players == 1 : 233 | g.display.text('D 1-Player', 0,30, 1) 234 | else : 235 | g.display.text('D 2-Player', 0,30, 1) 236 | g.display.text('R Frame/s {}'.format(g.frameRate), 0,40, 1) 237 | g.display.text('B + U/D Sound', 0, 50, 1) 238 | g.display.show() 239 | sleep_ms(10) 240 | g.getBtn() 241 | if g.setVol() : 242 | pass 243 | elif g.pressed (g.btnB) and g.justPressed(g.btnL) : 244 | exitGame = True 245 | gameOver= True 246 | break 247 | elif g.justPressed(g.btnA) or demoOn : 248 | if players == 0 : # demo 249 | onePlayer = False 250 | demo = True 251 | demoOn = True 252 | g.display.fill(0) 253 | g.display.text('DEMO', 5, 0, 1) 254 | g.display.text('B+L to Stop', 5, 30, 1) 255 | g.display.show() 256 | sleep_ms(1000) 257 | 258 | elif players == 1 : 259 | onePlayer = True 260 | demo = False 261 | else : 262 | onePlayer = False 263 | demo = False 264 | break 265 | elif g.justPressed(g.btnU) : 266 | usePaddle = not usePaddle 267 | elif g.justPressed(g.btnD) : 268 | players = (players + 1) % 3 269 | 270 | elif g.justPressed(g.btnR) : 271 | if g.pressed(g.btnB) : 272 | g.frameRate = g.frameRate - 5 if g.frameRate > 5 else 100 273 | else : 274 | g.frameRate = g.frameRate + 5 if g.frameRate < 100 else 5 275 | 276 | self.init(onePlayer, demo, usePaddle) 277 | 278 | #game loop 279 | 280 | while not gameOver: 281 | g.getBtn() 282 | if g.pressed (g.btnB) and g.justReleased(g.btnL) : 283 | gameOver = True 284 | demoOn = False 285 | 286 | 287 | self.check_ball_hits_bat() 288 | self.check_ball_hits_wall() 289 | 290 | # Redraw the screen. 291 | g.display.fill(0) 292 | 293 | for bat in self.bats: 294 | bat.move_bat(self.HEIGHT, self.bat_HEIGHT,self.balls) 295 | g.display.fill_rect(bat.x,bat.y,self.bat_WIDTH, self.bat_HEIGHT, self.COLOUR) 296 | 297 | for ball in self.balls: 298 | ball.move_ball() 299 | g.display.fill_rect(ball.x,ball.y,self.BALL_WIDTH ,self.BALL_WIDTH, self.COLOUR) 300 | 301 | 302 | g.display.text ('{} : {}'.format (scores[0], scores[1]), 45, 0, 1) 303 | 304 | if gameOver : 305 | g.display.fill_rect(25,25,100, 30,0) 306 | g.display.text ("Game Over", 30, 30, 1) 307 | g.display.show() 308 | g.playTone ('c5', 200) 309 | g.playTone ('g4', 200) 310 | g.playTone ('g4', 200) 311 | g.playTone ('a4', 200) 312 | g.playTone ('g4', 400) 313 | g.playTone ('b4', 200) 314 | g.playTone ('c5', 400) 315 | elif len(self.balls) < self.maxballs and g.random(0,10000) < self.ballschance : 316 | self.balls.append(Ball( 317 | self.BALL_VELOCITY, 318 | int(self.WIDTH / 2 - self.BALL_WIDTH / 2), 319 | int(self.HEIGHT / 2 - self.BALL_WIDTH / 2), 320 | self.BALL_WIDTH, 321 | self.BALL_WIDTH)) 322 | 323 | g.display_and_wait() 324 | 325 | 326 | #if __name__ == '__main__': 327 | pong = Pong() 328 | 329 | pong.game_loop() 330 | 331 | if g.ESP32 : 332 | g.deinit() 333 | del sys.modules["gameESP"] 334 | gc.collect() 335 | 336 | print ("game exit") 337 | -------------------------------------------------------------------------------- /invader.py: -------------------------------------------------------------------------------- 1 | # invaders.py 2 | # 3 | # Use common game module "gameESP.py" for ESP8266 or ESP32 4 | # by Billy Cheung 2019 10 26 5 | # 6 | # 7 | import gc 8 | import sys 9 | gc.collect() 10 | print (gc.mem_free()) 11 | import utime 12 | from utime import sleep_ms 13 | import network 14 | from math import sqrt 15 | # all dislplay, buttons, paddle, sound logics are in GameESP.mpy module 16 | from gameESP import * 17 | g=gameESP() 18 | 19 | # songbuf = [ g.songStart, NotesorFreq , timeunit, 20 | # freq1, duration1, freq2, duration2, 21 | # g.songLoop or g.songEnd] 22 | # Notes or Freq : False=song coded frequencies (Hz), True=song coded in notes, e.g. 'f4' 'f#4') 23 | # timeunit = value to multiple durations with that number of milli-seconds. Default 1 milli-second. 24 | # freq1 can be replaced with note, e.g. [g.songStart, 'c4', 200,'d4', 200,'e4',300,'f4', 300,'f#4': 300,'g4',300,g.songEnd] 25 | # freq1 = 0 for silence notes 26 | # duration1 is multipled with tempo to arrive at a duration for the note in millseconds 27 | 28 | g.frameRate = 30 29 | g.bgm = 1 30 | g.maxBgm = 3 31 | bgmBuf= [ 32 | [g.songStart, False, 1, g.songEnd], 33 | # Empire Strikes Back 34 | [ g.songStart,True, 100, 0, 4, 35 | 'g3',1,0,1,'g3',1,0,1,'g3',1,0,1,'c4',8,'g4',8,0,4,'f4',2,'e4',2,'d4',2,'c5',8,'g4',8, 0,4, 'f4',2,'e4',2,'d4',2,'c5',8,'g4',8,0,4,'f4',2,'e4',2,'f4',2,'d4',8,0,8, 36 | 'g3',1,0,1,'g3',1,0,1,'g3',1,0,1,'c4',8,'g4',8,0,4,'f4',2,'e4',2,'d4',2,'c5',8,'g4',8, 0,4, 'f4',2,'e4',2,'d4',2,'c5',8,'g4',8,0,4,'f4',2,'e4',2,'f4',2,'d4',8,0,8, 37 | 'g3',1,0,1,'g3',1,0,1,'a3',4,0,4,'f4',2,'e4',2,'d4',2,'c4',1,0,1,'c4',2,'d4',1,'e4',1,'d4',2,'a3',2,'b3',4, 38 | 'g3',1,0,1,'g3',1,0,1,'a3',4,0,4,'f4',2,'e4',2,'d4',2,'c4',1,0,1,'g4',2,0,1,'d4',1,'d4',4,0,4, 39 | 'g3',1,0,1,'g3',1,0,1,'a3',4,0,4,'f4',2,'e4',2,'d4',2,'c4',1,0,1,'c4',2,'d4',1,'e4',1,'d4',2,'a3',2,'b3',4, 40 | 'e4',1,0,1,'e4',2,'a4',2,'g4',2,'f4',2,'e4',2,'d4',2,'c4',2,'b3',2,'a3',2,'e4',8, 0, 8, 41 | g.songLoop], 42 | # The Imperial March 43 | [ g.songStart,False, 1, 0, 400, 44 | 440, 400, 0, 100, 440, 400, 0, 100, 440, 400, 0,100, 349, 350, 523, 150, 440, 500, 349, 350, 523, 150, 440, 650, 0,500, 659, 500, 659, 500, 659, 500, 698, 350, 523, 150, 415, 500, 349, 350, 523, 150, 440, 650, 0, 500, 45 | 880, 500, 440, 300, 440, 150, 880, 500, 830, 325, 784, 175, 740, 125, 698, 125, 740, 250, 0, 325, 445, 250, 622, 500, 587, 325, 554, 175, 523, 125, 466, 125, 523, 250, 0, 350, 46 | 349, 250, 415, 500, 349, 350, 440, 125, 523, 500, 440, 375, 523, 125, 659, 650, 0, 500,349, 250, 415, 500, 349, 375, 523, 125, 440, 500, 349, 375, 523, 125, 440, 650,0, 650, 47 | 880, 500, 440, 300, 440, 150, 880, 500, 830, 325, 784, 175, 740, 125, 698, 125, 740, 250, 0, 325, 445, 250, 622, 500, 587, 325, 554, 175, 523, 125, 466, 125, 523, 250, 0, 350, 48 | 349, 250, 415, 500, 349, 350, 440, 125, 523, 500, 440, 375, 523, 125, 659, 650, 0, 500,349, 250, 415, 500, 349, 375, 523, 125, 440, 500, 349, 375, 523, 125, 440, 650,0, 650, 49 | g.songLoop], 50 | # Tetris 51 | [ g.songStart,False,200, 0, 4, 52 | 659,2, 494, 1, 523,1, 587,2, 523, 1, 493, 1, 440, 2, 440, 1, 523,1, 659,2,587,1,523,1,493,2, 493,1,523,1,587,2,659,2,523,2,440,2,440,2,0,2,587, 1,698,1,880,2,783,1,698,1,659,2,523,1,659,2,587,1,523,1,493,2,493,1,523,1,587,2,659,2,523,2,440,2,440,2,0,2, 53 | 329,4,261,4,293,4,246,4,261,4,220,4,207,4,246,4,329,4,261,4,293,4,246,4,261,2,329,2,440,4,415,6,0,2, 54 | g.songLoop] 55 | ] 56 | 57 | xMargin = const (5) 58 | yMargin = const(10) 59 | screenL = const (5) 60 | screenR = const(117) 61 | screenT = const (10) 62 | screenB = const (58) 63 | dx = 5 64 | vc = 3 65 | gunW= const(5) 66 | gunH = const (5) 67 | invaderSize = const(4) 68 | invaders_rows = const(5) 69 | invaders_per_row = const(11) 70 | 71 | 72 | 73 | def setUpInvaders (): 74 | y = yMargin 75 | while y < yMargin + (invaderSize+2) * invaders_rows : 76 | x = xMargin 77 | while x < xMargin + (invaderSize+2) * invaders_per_row : 78 | invaders.append(Rect(x,y,invaderSize, invaderSize)) 79 | x = x + invaderSize + 2 80 | y = y + invaderSize + 2 81 | 82 | def drawSpaceships (posture) : 83 | if posture : 84 | for i in spaceships : 85 | g.display.fill_rect(i.x+2, i.y, 5 , 3, 1) 86 | g.display.fill_rect(i.x, i.y+1, 9, 1, 1) 87 | g.display.fill_rect(i.x+1, i.y+1, 2, 1, 0) 88 | else : 89 | for i in spaceships : 90 | g.display.fill_rect(i.x+2, i.y, 5 , 3, 1) 91 | g.display.fill_rect(i.x, i.y+1, 9, 1, 1) 92 | g.display.fill_rect(i.x+5, i.y+1, 2, 1, 0) 93 | 94 | def drawInvaders (posture) : 95 | if posture : 96 | for i in invaders : 97 | g.display.fill_rect(i.x, i.y, invaderSize , invaderSize, 1) 98 | g.display.fill_rect(i.x+1, i.y+2, invaderSize-2, invaderSize-2, 0) 99 | else : 100 | for i in invaders : 101 | g.display.fill_rect(i.x, i.y, invaderSize , invaderSize, 1) 102 | g.display.fill_rect(i.x+1, i.y, invaderSize-2, invaderSize-2, 0) 103 | def drawGun () : 104 | g.display.fill_rect(gun.x+2, gun.y, 1, 2,1) 105 | g.display.fill_rect(gun.x, gun.y+2, gunW, 3,1) 106 | 107 | def drawBullets () : 108 | for b in bullets: 109 | g.display.fill_rect(b.x, b.y, 1,3,1) 110 | 111 | def drawAbullets () : 112 | for b in aBullets: 113 | g.display.fill_rect(b.x, b.y, 1,3,1) 114 | 115 | def drawScore () : 116 | g.display.text('S:{}'.format (score), 0,0,1) 117 | g.display.text('L:{}'.format (level), 50,0,1) 118 | for i in range (0, life) : 119 | g.display.fill_rect(92 + (gunW+2)*i, 0, 1, 2,1) 120 | g.display.fill_rect(90 + (gunW+2)*i, 2, gunW, 3,1) 121 | 122 | 123 | 124 | exitGame = False 125 | demoOn = False 126 | while not exitGame: 127 | gameOver = False 128 | usePaddle = False 129 | if demoOn : 130 | demo = True 131 | else : 132 | demo = False 133 | life = 3 134 | 135 | g.startSong(bgmBuf[g.bgm]) 136 | #menu screen 137 | while True: 138 | g.display.fill(0) 139 | g.display.text('Invaders', 0, 0, 1) 140 | g.display.rect(90,0, g.max_vol*4+2,6,1) 141 | g.display.fill_rect(91,1, g.vol * 4,4,1) 142 | g.display.text('A Start B+L Quit', 0, 10, 1) 143 | if usePaddle : 144 | g.display.text('U Paddle', 0,20, 1) 145 | else : 146 | g.display.text('U Button', 0,20, 1) 147 | if demo : 148 | g.display.text('D AI-Player', 0,30, 1) 149 | else : 150 | g.display.text('D 1-Player', 0,30, 1) 151 | g.display.text('R Frame/s {}'.format(g.frameRate), 0,40, 1) 152 | if g.bgm : 153 | g.display.text('L Music {}'.format(g.bgm), 0, 50, 1) 154 | else : 155 | g.display.text('L Music Off', 0, 50, 1) 156 | # g.display.text('B + U/D Sound', 0, 60, 1) 157 | g.display.show() 158 | sleep_ms(10) 159 | g.getBtn() 160 | if g.setVol() : 161 | pass 162 | elif g.setFrameRate() : 163 | pass 164 | elif g.pressed(g.btnB) and g.justPressed (g.btnL) : 165 | exitGame = True 166 | gameOver= True 167 | break 168 | elif g.justPressed(g.btnA) or demoOn : 169 | if demo : 170 | demoOn = True 171 | g.display.fill(0) 172 | g.display.text('DEMO', 5, 0, 1) 173 | g.display.text('B+L to Stop', 5, 30, 1) 174 | g.display.show() 175 | sleep_ms(1000) 176 | break 177 | elif g.justPressed(g.btnU) : 178 | usePaddle = not usePaddle 179 | elif g.justPressed(g.btnD) : 180 | demo = not demo 181 | elif g.justPressed(g.btnL) : 182 | g.bgm = 0 if g.bgm >= g.maxBgm else g.bgm + 1 183 | if g.bgm : 184 | g.startSong(bgmBuf[g.bgm]) 185 | else : 186 | g.stopSong() 187 | #reset the game 188 | score = 0 189 | frameCount = 0 190 | level = 0 191 | loadLevel = True 192 | postureA = False 193 | postureS = False 194 | # Chance from 1 to 128 195 | aBulletChance = 0 196 | spaceshipChance = 1 197 | 198 | while not gameOver: 199 | 200 | lost = False 201 | frameCount = (frameCount + 1 ) % 120 202 | g.display.fill(0) 203 | 204 | if loadLevel : 205 | loadLevel = False 206 | spaceships = [] 207 | invaders = [] 208 | bullets = [] 209 | aBullets = [] 210 | setUpInvaders() 211 | gun = Rect(screenL+int((screenR-screenL)/2), screenB, gunW, gunH) 212 | aBulletChance = 5 + level * 5 213 | 214 | 215 | 216 | #generate space ships 217 | if g.random (0,99) < spaceshipChance and len(spaceships) < 1 : 218 | spaceships.append(Rect(0,9, 9, 9)) 219 | 220 | if len(spaceships) : 221 | if not frameCount % 3 : 222 | postureS = not postureS 223 | # move spaceships once every 4 frames 224 | for i in spaceships: 225 | i.move(2,0) 226 | if i.x >= screenR : 227 | spaceships.remove(i) 228 | if not g.bgm : 229 | # only play sound effect if no background music 230 | if frameCount % 20 == 10 : 231 | g.playTone ('e5', 20) 232 | elif frameCount % 20 == 0 : 233 | g.playTone ('c5', 20) 234 | 235 | 236 | if not frameCount % 15 : 237 | postureA = not postureA 238 | # move Aliens once every 15 frames 239 | if not g.bgm : 240 | # only play sound effect if no background music 241 | if postureA : 242 | g.playSound (80, 10) 243 | else: 244 | g.playSound (120, 10) 245 | for i in invaders: 246 | if i.x > screenR or i.x < screenL : 247 | dx = -dx 248 | for alien in invaders : 249 | alien.move (0, invaderSize) 250 | if alien.y + alien.h > gun.y : 251 | lost = True 252 | loadLevel = True 253 | g.playTone ('f4',300) 254 | g.playTone ('d4',100) 255 | g.playTone ('c5',100) 256 | break 257 | break 258 | 259 | for i in invaders : 260 | i.move (dx, 0) 261 | 262 | 263 | g.getBtn() 264 | 265 | if g.pressed (g.btnB) and g.justReleased(g.btnL) : 266 | gameOver= True 267 | demoOn = False 268 | break 269 | 270 | if demo : 271 | 272 | if g.random (0,1) and len(bullets) < 2: 273 | bullets.append(Rect(gun.x+3, gun.y-1, 1, 3)) 274 | g.playSound (200,5) 275 | g.playSound (300,5) 276 | g.playSound (400,5) 277 | 278 | if g.random(0,1) : 279 | vc = 3 280 | else : 281 | vc = -3 282 | 283 | if (vc + gun.x + gunW) < g.screenW and (vc + gun.x) >= 0 : 284 | gun.move (vc, 0) 285 | 286 | # Real player 287 | elif g.pressed (g.btnA | g.btnB) and len(bullets) < 2: 288 | bullets.append(Rect(gun.x+3, gun.y-1, 1, 3)) 289 | g.playSound (200,5) 290 | g.playSound (300,5) 291 | g.playSound (400,5) 292 | # move gun 293 | 294 | 295 | elif usePaddle : 296 | gun.x = int(g.getPaddle() / (1024/(screenR-screenL))) 297 | gun.x2 = gun.x+gunW-1 298 | else : 299 | if g.pressed (g.btnL) and gun.x - 3 > 0 : 300 | vc = -3 301 | elif g.pressed(g.btnR) and (gun.x + 3 + gunW ) < g.screenW : 302 | vc = 3 303 | else : 304 | vc = 0 305 | gun.move (vc, 0) 306 | 307 | # move bullets 308 | 309 | for b in bullets: 310 | b.move(0,-3) 311 | if b.y < 0 : 312 | bullets.remove(b) 313 | else : 314 | for i in invaders: 315 | if i.colliderect(b) : 316 | invaders.remove(i) 317 | bullets.remove(b) 318 | score +=1 319 | g.playTone ('c6',10) 320 | break 321 | for i in spaceships : 322 | if i.colliderect(b) : 323 | spaceships.remove(i) 324 | bullets.remove(b) 325 | score +=10 326 | g.playTone ('b4',30) 327 | g.playTone ('e5',10) 328 | g.playTone ('c4',30) 329 | break 330 | 331 | # Launch Alien bullets 332 | for i in invaders: 333 | if g.random (0,1000) * len (invaders) * 10 < aBulletChance and len(aBullets) < 3 : 334 | aBullets.append(Rect(i.x+2, i.y, 1, 3)) 335 | 336 | # move Alien bullets 337 | for b in aBullets: 338 | b.move(0,3) 339 | if b.y > g.screenH : 340 | aBullets.remove(b) 341 | elif b.colliderect(gun) : 342 | lost = True 343 | #print ('{} {} {} {} : {} {} {} {}'.format(b.x,b.y,b.x2,b.y2,gun.x,gun.y,gun.x2,gun.y2)) 344 | aBullets.remove(b) 345 | g.playTone ('c5',30) 346 | g.playTone ('e4',30) 347 | g.playTone ('b4',30) 348 | break 349 | 350 | drawSpaceships (postureS) 351 | drawInvaders (postureA) 352 | drawGun() 353 | drawBullets() 354 | drawAbullets() 355 | drawScore() 356 | 357 | 358 | if len(invaders) == 0 : 359 | level += 1 360 | loadLevel = True 361 | g.playTone ('c4',100) 362 | g.playTone ('d4',100) 363 | g.playTone ('e4',100) 364 | g.playTone ('f4',100) 365 | g.playTone ('g4',100) 366 | g.bgm = 0 if g.bgm >= g.maxBgm else g.bgm + 1 367 | if g.bgm : 368 | g.startSong(bgmBuf[g.bgm]) 369 | 370 | if lost : 371 | lost = False; 372 | life -= 1 373 | g.playTone ('f4',100) 374 | g.playTone ('g4',100) 375 | g.playTone ('c4',100) 376 | g.playTone ('d4',100) 377 | sleep_ms (1000) 378 | if life < 0 : 379 | gameOver = True 380 | 381 | if gameOver : 382 | g.display.fill_rect (3, 15, 120,20,0) 383 | g.display.text ("GAME OVER", 5, 20, 1) 384 | g.playTone ('b4',300) 385 | g.playTone ('e4',100) 386 | g.playTone ('c4',100) 387 | g.display.show() 388 | sleep_ms(2000) 389 | 390 | g.display_and_wait() 391 | 392 | g.deinit() 393 | if g.ESP32 : 394 | del sys.modules["gameESP"] 395 | gc.collect() 396 | -------------------------------------------------------------------------------- /snake.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------- 2 | # snake.py Game 3 | ## Use common game module "gameESP.py" for ESP8266 or ESP32 4 | # by Billy Cheung 2019 10 26import gc 5 | import sys 6 | import gc 7 | gc.collect() 8 | # # print (gc.mem_free()) 9 | import network 10 | import utime 11 | from utime import sleep_ms 12 | # all dislplay, buttons, paddle, sound logics are in gameESP.mpy module 13 | from gameESP import * 14 | g=gameESP() 15 | 16 | SNAKE_SIZE = 4 17 | SNAKE_LENGTH = 4 18 | SNAKE_EXTENT = 2 19 | COLS = 0 20 | ROWS = 0 21 | OX = 0 22 | OY = 0 23 | COLOR_BG = 0 24 | COLOR_WALL = 1 25 | COLOR_SNAKE = 1 26 | COLOR_APPLE = 1 27 | COLOR_SCORE = 1 28 | COLOR_LOST_BG = 1 29 | COLOR_LOST_FG = 0 30 | MODE_MENU = 0 31 | MODE_START = 1 32 | MODE_READY = 2 33 | MODE_PLAY = 3 34 | MODE_LOST = 4 35 | MODE_GAMEOVER = 5 36 | MODE_EXIT = 6 37 | 38 | 39 | # ---------------------------------------------------------- 40 | # Game management 41 | # ---------------------------------------------------------- 42 | 43 | def tick(): 44 | handleButtons() 45 | 46 | if not game['refresh']: 47 | clearSnakeTail() 48 | if game['mode'] == MODE_PLAY: 49 | moveSnake() 50 | if game['refresh']: 51 | game['refresh'] = False 52 | if didSnakeEatApple(): 53 | g.playTone('d6', 20) 54 | g.playTone('c5', 20) 55 | g.playTone('f4', 20) 56 | game['score'] += 1 57 | game['refresh'] = True 58 | extendSnakeTail() 59 | spawnApple() 60 | if didSnakeBiteItsTail() or didSnakeHitTheWall(): 61 | g.playTone('c4', 500) 62 | game['mode'] = MODE_LOST 63 | game['refresh'] = True 64 | elif game['mode'] == MODE_LOST: 65 | # print ('LOST') 66 | game['life'] -= 1 67 | 68 | if game['life'] <= 0 : 69 | game['mode'] = MODE_GAMEOVER 70 | else : 71 | game['mode'] = MODE_START 72 | # print (game['mode']) 73 | sleep_ms(1000) 74 | elif game['mode'] == MODE_GAMEOVER: 75 | # print('gameOver') 76 | game['mode'] = MODE_MENU 77 | g.playTone('c4', 100) 78 | g.playTone('e4', 100) 79 | g.playTone('g4', 100) 80 | sleep_ms(1000) 81 | elif game['mode'] == MODE_MENU: 82 | # print('Menu') 83 | pass 84 | elif game['mode'] == MODE_START: 85 | # print ("======================") 86 | game['refresh'] = True 87 | resetSnake() 88 | spawnApple() 89 | game['mode'] = MODE_READY 90 | game['score'] = 0 91 | game['time'] = 0 92 | if game['demo'] : 93 | game['demoOn'] = True 94 | elif game['mode'] == MODE_READY: 95 | # print ("READY") 96 | game['refresh'] = False 97 | moveSnake() 98 | if snakeHasMoved(): 99 | g.playTone('c5', 100) 100 | game['mode'] = MODE_PLAY 101 | elif game['mode'] == MODE_EXIT: 102 | return 103 | else: 104 | handleButtons() 105 | 106 | draw() 107 | game['time'] += 1 108 | 109 | 110 | def spawnApple(): 111 | apple['x'] = g.random (1, COLS - 2) 112 | apple['y'] = g.random (1, ROWS - 2) 113 | 114 | def smart(): 115 | if g.random(0,199) < 200 : 116 | return True 117 | return False 118 | 119 | def noCrash (x,y): 120 | h = snake['head'] 121 | n = snake['len'] 122 | # hit walls ? 123 | if x < 0 or x > COLS-1 or y < 0 or y > ROWS-1: 124 | return False 125 | # hit snake body ? 126 | for i in range(n): 127 | if i !=h and snake['x'][i] == x and snake['y'][i] == y: 128 | return False 129 | i = (i + 1) % n 130 | return True 131 | 132 | def handleButtons(): 133 | global SNAKE_SIZE 134 | g.getBtn() 135 | 136 | if game['mode'] == MODE_MENU : 137 | sleep_ms(10) 138 | if g.setVol() : 139 | pass 140 | elif g.justPressed(g.btnU): 141 | SNAKE_SIZE = 4 if SNAKE_SIZE == 2 else 6 if SNAKE_SIZE == 4 else 2 142 | g.playTone('c5', 100) 143 | elif g.justPressed(g.btnR) : 144 | g.playTone('d5', 100) 145 | if g.pressed(g.btnB) : 146 | g.frameRate = g.frameRate - 5 if g.frameRate > 5 else 100 147 | else : 148 | g.frameRate = g.frameRate + 5 if g.frameRate < 100 else 5 149 | elif g.justPressed(g.btnD): 150 | game['demo'] = not game['demo'] 151 | g.playTone('e5', 100) 152 | elif g.justReleased(g.btnA) or game['demoOn'] : 153 | game['mode'] = MODE_START 154 | game['life'] = 3 155 | game['reset'] = True 156 | g.playTone('f5', 100) 157 | if game['demo'] : 158 | g.display.fill(0) 159 | g.display.text('DEMO', 5, 0, 1) 160 | g.display.text('B to Stop', 5, 30, 1) 161 | g.display.show() 162 | sleep_ms(1000) 163 | 164 | elif g.pressed(g.btnL) : 165 | game['mode'] = MODE_EXIT 166 | g.playTone('g5', 100) 167 | else : 168 | if game['demo'] : 169 | if g.justReleased (g.btnB): 170 | game['demoOn'] = False 171 | game['mode'] = MODE_GAMEOVER 172 | g.playTone('g5', 100) 173 | g.playTone('f5', 100) 174 | g.playTone('e5', 100) 175 | 176 | #get snake's head position 177 | 178 | h = snake['head'] 179 | Hx = snake['x'][h] 180 | Hy = snake['y'][h] 181 | #get snake's neck position 182 | # # print ("h={} {}:{} C={} R={}".format (h,Hx,Hy, COLS, ROWS)) 183 | 184 | # move closer to the apple, if smart enough 185 | if Hx < apple['x'] and smart() and noCrash(Hx+1, Hy): 186 | dirSnake(1, 0) 187 | # # print ("A") 188 | elif Hx > apple['x'] and smart() and noCrash(Hx-1, Hy): 189 | dirSnake(-1, 0) 190 | # # print ("B") 191 | elif Hy < apple['y'] and smart() and noCrash(Hx, Hy+1): 192 | dirSnake(0, 1) 193 | # # print ("C") 194 | elif Hy > apple['y'] and smart() and noCrash(Hx, Hy-1): 195 | dirSnake(0, -1) 196 | # # print ("D") 197 | elif noCrash(Hx+1, Hy): 198 | dirSnake(1, 0) 199 | # # print ("E") 200 | elif noCrash(Hx-1, Hy): 201 | dirSnake(-1, 0) 202 | # # print ("F") 203 | elif noCrash(Hx, Hy+1): 204 | dirSnake(0, 1) 205 | # # print ("G") 206 | elif noCrash(Hx, Hy-1): 207 | dirSnake(0, -1) 208 | # # print ("H") 209 | else : 210 | if g.justPressed (g.btnL): 211 | dirSnake(-1, 0) 212 | elif g.justPressed(g.btnR): 213 | dirSnake(1, 0) 214 | elif g.justPressed(g.btnU): 215 | dirSnake(0, -1) 216 | elif g.justPressed(g.btnD): 217 | dirSnake(0, 1) 218 | elif g.justPressed(g.btnA): 219 | if snake['vx'] == 1: 220 | dirSnake(0, 1) 221 | elif snake['vx'] == -1: 222 | dirSnake(0, -1) 223 | elif snake['vy'] == 1: 224 | dirSnake(-1, 0) 225 | elif snake['vy'] == -1: 226 | dirSnake(1, 0) 227 | elif snake['vx']==0 and snake['vy']==0 : 228 | dirSnake(0, 1) 229 | elif g.justPressed(g.btnB): 230 | if snake['vx'] == 1: 231 | dirSnake(0, -1) 232 | elif snake['vx'] == -1: 233 | dirSnake(0, 1) 234 | elif snake['vy'] == 1: 235 | dirSnake(1, 0) 236 | elif snake['vy'] == -1: 237 | dirSnake(-1, 0) 238 | elif snake['vx']==0 and snake['vy']==0 : 239 | dirSnake(1, 0) 240 | 241 | 242 | 243 | 244 | # ---------------------------------------------------------- 245 | # Snake management 246 | # ---------------------------------------------------------- 247 | 248 | def resetSnake(): 249 | global COLS, ROWS, OX, OY 250 | COLS = (g.screenW - 4) // SNAKE_SIZE 251 | ROWS = (g.screenH - 4) // SNAKE_SIZE 252 | OX = (g.screenW - COLS * SNAKE_SIZE) // 2 253 | OY = (g.screenH - ROWS * SNAKE_SIZE) // 2 254 | x = COLS // SNAKE_SIZE 255 | y = ROWS // SNAKE_SIZE 256 | snake['vx'] = 0 257 | snake['vy'] = 0 258 | # print (game['reset']) 259 | if game['reset'] : 260 | game['reset'] = False 261 | s = SNAKE_LENGTH 262 | else : 263 | s = snake['len'] 264 | 265 | snake['x'] = [] 266 | snake['y'] = [] 267 | for _ in range(s): 268 | snake['x'].append(x) 269 | snake['y'].append(y) 270 | snake['head'] = s - 1 271 | snake['len'] = s 272 | 273 | 274 | def dirSnake(dx, dy): 275 | snake['vx'] = dx 276 | snake['vy'] = dy 277 | 278 | def moveSnake(): 279 | h = snake['head'] 280 | x = snake['x'][h] 281 | y = snake['y'][h] 282 | h = (h + 1) % snake['len'] 283 | snake['x'][h] = x + snake['vx'] 284 | snake['y'][h] = y + snake['vy'] 285 | snake['head'] = h 286 | 287 | def snakeHasMoved(): 288 | return snake['vx'] or snake['vy'] 289 | 290 | def didSnakeEatApple(): 291 | h = snake['head'] 292 | return snake['x'][h] == apple['x'] and snake['y'][h] == apple['y'] 293 | 294 | def extendSnakeTail(): 295 | i = snake['head'] 296 | n = snake['len'] 297 | i = (i + 1) % n 298 | x = snake['x'][i] 299 | y = snake['y'][i] 300 | for _ in range(SNAKE_EXTENT): 301 | snake['x'].insert(i, x) 302 | snake['y'].insert(i, y) 303 | snake['len'] += SNAKE_EXTENT 304 | 305 | def didSnakeBiteItsTail(): 306 | h = snake['head'] 307 | n = snake['len'] 308 | x = snake['x'][h] 309 | y = snake['y'][h] 310 | i = (h + 1) % n 311 | for _ in range(n-1): 312 | if snake['x'][i] == x and snake['y'][i] == y: 313 | return True 314 | i = (i + 1) % n 315 | return False 316 | 317 | 318 | 319 | def didSnakeHitTheWall(): 320 | h = snake['head'] 321 | x = snake['x'][h] 322 | y = snake['y'][h] 323 | return x < 0 or x == COLS or y < 0 or y == ROWS 324 | 325 | # ---------------------------------------------------------- 326 | # Graphic display 327 | # ---------------------------------------------------------- 328 | 329 | def draw(): 330 | if game['mode'] == MODE_MENU: 331 | drawGameMenu() 332 | else : 333 | if game['mode'] == MODE_LOST: 334 | if game['life'] == 0 : 335 | drawGameover() 336 | elif game['refresh']: 337 | clearScreen() 338 | drawWalls() 339 | drawSnake() 340 | else: 341 | drawSnakeHead() 342 | drawScore() 343 | drawApple() 344 | g.display.show() 345 | 346 | def clearScreen(): 347 | color = COLOR_LOST_BG if game['mode'] == MODE_LOST else COLOR_BG 348 | g.display.fill(color) 349 | 350 | def drawGameMenu(): 351 | global SNAKE_SIZE 352 | clearScreen() 353 | g.display.text('Snake', 0, 0, 1) 354 | g.display.rect(90,0, g.max_vol*4+2,6,1) 355 | g.display.fill_rect(91,1, g.vol * 4,4,1) 356 | 357 | g.display.text("A START U+D EXIT",0,10,1) 358 | if game['demo'] : 359 | g.display.text('D AI-Player', 0,20, 1) 360 | else : 361 | g.display.text('D 1-PLAYER', 0,20, 1) 362 | g.display.text("U SIZE {}".format(SNAKE_SIZE),0,30,1) 363 | g.display.text("R FRAME {}".format(g.frameRate),0,40,1) 364 | g.display.text("B + U/D VOLUME",0,50,1) 365 | 366 | 367 | def drawGameover(): 368 | g.display.fill_rect(20,20,100,30,0) 369 | g.display.text("GAME OVER",20,20,1) 370 | 371 | 372 | def drawWalls(): 373 | color = COLOR_LOST_FG if game['mode'] == MODE_LOST else COLOR_WALL 374 | g.display.rect(0, 0, g.screenW, g.screenH,color) 375 | 376 | def debugSnake(): 377 | n = snake['len'] 378 | i = snake['head'] 379 | for _ in range(n): 380 | 381 | # # print(snake['x'][i], snake['y'][i]) 382 | if (i - 1) < 0 : 383 | i=n-1 384 | else : 385 | i-=1 386 | 387 | 388 | def drawSnake(): 389 | isTimeToBlink = game['time'] % 4 < 2 390 | color = COLOR_LOST_FG if game['mode'] == MODE_LOST and isTimeToBlink else COLOR_SNAKE 391 | h = snake['head'] 392 | n = snake['len'] 393 | for i in range(n): 394 | if i == h : 395 | drawBox(snake['x'][i], snake['y'][i], color) 396 | else : 397 | drawDot(snake['x'][i], snake['y'][i], color) 398 | 399 | def drawSnakeHead(): 400 | h = snake['head'] 401 | drawBox(snake['x'][h], snake['y'][h], COLOR_SNAKE) 402 | 403 | def clearSnakeTail(): 404 | h = snake['head'] 405 | n = snake['len'] 406 | t = (h + 1) % n 407 | drawDot(snake['x'][t], snake['y'][t], COLOR_BG) 408 | 409 | def drawScore(): 410 | g.display.text('S {}'.format(game['score'] ),5,0,1) 411 | g.display.text('L {}'.format( game['life'] ),80,0,1) 412 | 413 | def drawApple(): 414 | drawBox(apple['x'], apple['y'], COLOR_APPLE) 415 | g.display.rect(OX + apple['x']* SNAKE_SIZE + int(SNAKE_SIZE/2), OY + apple['y'] * SNAKE_SIZE-1, 1,1, COLOR_APPLE) 416 | 417 | def drawDot(x, y, color): 418 | g.display.fill_rect(OX + x * SNAKE_SIZE, OY + y * SNAKE_SIZE, SNAKE_SIZE, SNAKE_SIZE,color) 419 | 420 | def drawBox(x, y, color): 421 | g.display.rect(OX + x * SNAKE_SIZE, OY + y * SNAKE_SIZE, SNAKE_SIZE, SNAKE_SIZE,color) 422 | 423 | 424 | # ---------------------------------------------------------- 425 | # Initialization 426 | # ---------------------------------------------------------- 427 | 428 | 429 | game = { 430 | 'mode': MODE_MENU, 431 | 'score': 0, 432 | 'life': 0, 433 | 'time': 0, 434 | 'refresh': True, 435 | 'reset': True, 436 | 'demo': False, 437 | 'demoOn' : False 438 | } 439 | 440 | snake = { 441 | 'x': [], 442 | 'y': [], 443 | 'head': 0, 444 | 'len': 0, 445 | 'vx': 0, 446 | 'vy': 0 447 | } 448 | 449 | apple = { 'x': 0, 'y': 0 } 450 | 451 | # ---------------------------------------------------------- 452 | # Main loop 453 | # ---------------------------------------------------------- 454 | while game['mode'] != MODE_EXIT : 455 | tick() 456 | g.display_and_wait() 457 | 458 | if g.ESP32 : 459 | g.deinit() 460 | del sys.modules["gameESP"] 461 | gc.collect() 462 | -------------------------------------------------------------------------------- /game32.py: -------------------------------------------------------------------------------- 1 | # gameESP.py 2 | # for ESP32 3 | # 4 | # gameESP-micropython 5 | # Simple MicroPython game modules and sample games for ESP8266 and ESP32 6 | # 7 | # gameESP.py for esp8266 or esp32 8 | # if you are using esp8266 boards, copy game8266.py as gameESP.py or use mpy-cross to compile to gameESP.mpy 9 | # if you are using esp32 boards, copy game32.py as gameESP.py or use mpy-cross to compile to gameESP.mpy 10 | # 11 | # common micropython module for ESP8266 game board designed by Billy Cheung (c) 2019 08 31 12 | # --usage-- 13 | # Using this common micropython game module, you can write micropython games to run 14 | # either on the SPI OLED or I2C OLED without chaning a line of code. 15 | # You only need to set the following line in gameESP.py file at the __init__ function 16 | # self.useSPI = True # for SPI display , with buttons read through ADC 17 | # self.useSPI = False # for I2C display, and individual hard buttons 18 | # 19 | # Note: esp8266 is very bad at running .py micropython source code files 20 | # with its very limited CPU onboard memory of 32K 21 | # so to run any program with > 300 lines of micropython codes combined (including all modules), 22 | # you need to convert source files into byte code first to avoid running out of memory. 23 | # Install a version of the mpy-cross micropython pre-compiler that can run in your system (available from github). 24 | # Type this command to convert gameESP.py to the byte code file gameESP.mpy using mpy-cross. 25 | # mpy-cross gameESP.py 26 | # then copy the gameESP.mpy file to the micropython's import directory on the flash 27 | # create your game and leaverge the functions to display, read buttons and paddle and make sounds 28 | # from the gameESP class module. 29 | # Add this line to your micropython game source code (examples attached, e.g. invader.py) 30 | # from gameESP import gameESP, Rect 31 | # g=gameESP() 32 | # 33 | # 34 | # 35 | #================================================================================== 36 | # ESP32 Game board 37 | # ----------------- 38 | # The pin layout is exactly the same as that of the Odroid-Go 39 | # so this library can be used on the micropython firmware of the Odroid-Go 40 | # 41 | #------------------------ 42 | # ESP32 OLED SPI SSD1306 43 | # ============== 44 | # VCC - 3.3V 45 | # GND - GND 46 | # D0/SCK - IO18-VSPI-SCK 47 | # D1/MOSI - IO23-VSPI-MOSI 48 | # RES - IO4 for ESP32 49 | # DC - IO21 50 | # CS - IO5-VSPI CS0 51 | # LED/BLK - IO14 52 | # 53 | # MISO - IO19-VSPI-MISO (not required for OLED) 54 | # 55 | # 56 | # TF Card Odroid-go (optional) 57 | # ================ 58 | # CS - IO22 VSPI CS1 59 | # MOSI - IO23 VSPI MOSI 60 | # MISO - IO19 VSPI SCK 61 | # SCK - IO18 VSPI MISO 62 | # 63 | # ESP32 OLED I2C SSD1306 64 | # ================ 65 | # VCC - 3.3V 66 | # GND - GND 67 | # SCL - IO 22 68 | # SDA IO 21 69 | 70 | # Audio 71 | # ====== 72 | # Speaker- - GND 73 | # Speaker+ - 10K VR- IO26 74 | 75 | # Paddle (10K VR) 76 | # ====== 77 | # left GND 78 | # middle VN/IO39 79 | # right VCC 80 | 81 | 82 | # D-PAD Buttons 83 | # ============= 84 | # tie one end to 3V3 85 | # UP IO35-10K-GND 86 | # Down-10K IO35 87 | # Left IO34-10K-GND 88 | # Right-10K IO34 89 | 90 | # Other Buttons 91 | # ============ 92 | # tie one end to GND 93 | # Menu IO13 94 | # Volume IO00-10K-3v3 95 | # Select IO27 96 | # Start IO39(VN)-10K-3v3 97 | # B IO33 98 | # A IO32 99 | # 100 | #----------------------------------------- 101 | import utime 102 | from utime import sleep_ms,ticks_ms, ticks_us, ticks_diff 103 | from machine import Pin, SPI,I2C, PWM, ADC 104 | from random import getrandbits, seed 105 | 106 | 107 | 108 | # MicroPython SSD1306 OLED driver, I2C and SPI interfaces 109 | 110 | from micropython import const 111 | import framebuf 112 | 113 | 114 | # register definitions 115 | SET_CONTRAST = const(0x81) 116 | SET_ENTIRE_ON = const(0xa4) 117 | SET_NORM_INV = const(0xa6) 118 | SET_DISP = const(0xae) 119 | SET_MEM_ADDR = const(0x20) 120 | SET_COL_ADDR = const(0x21) 121 | SET_PAGE_ADDR = const(0x22) 122 | SET_DISP_START_LINE = const(0x40) 123 | SET_SEG_REMAP = const(0xa0) 124 | SET_MUX_RATIO = const(0xa8) 125 | SET_COM_OUT_DIR = const(0xc0) 126 | SET_DISP_OFFSET = const(0xd3) 127 | SET_COM_PIN_CFG = const(0xda) 128 | SET_DISP_CLK_DIV = const(0xd5) 129 | SET_PRECHARGE = const(0xd9) 130 | SET_VCOM_DESEL = const(0xdb) 131 | SET_CHARGE_PUMP = const(0x8d) 132 | 133 | # Subclassing FrameBuffer provides support for graphics primitives 134 | # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html 135 | class SSD1306(framebuf.FrameBuffer): 136 | def __init__(self, width, height, external_vcc): 137 | self.width = width 138 | self.height = height 139 | self.external_vcc = external_vcc 140 | self.pages = self.height // 8 141 | self.buffer = bytearray(self.pages * self.width) 142 | super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) 143 | self.init_display() 144 | 145 | def init_display(self): 146 | for cmd in ( 147 | SET_DISP | 0x00, # off 148 | # address setting 149 | SET_MEM_ADDR, 0x00, # horizontal 150 | # resolution and layout 151 | SET_DISP_START_LINE | 0x00, 152 | SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 153 | SET_MUX_RATIO, self.height - 1, 154 | SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 155 | SET_DISP_OFFSET, 0x00, 156 | SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12, 157 | # timing and driving scheme 158 | SET_DISP_CLK_DIV, 0x80, 159 | SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1, 160 | SET_VCOM_DESEL, 0x30, # 0.83*Vcc 161 | # display 162 | SET_CONTRAST, 0xff, # maximum 163 | SET_ENTIRE_ON, # output follows RAM contents 164 | SET_NORM_INV, # not inverted 165 | # charge pump 166 | SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, 167 | SET_DISP | 0x01): # on 168 | self.write_cmd(cmd) 169 | self.fill(0) 170 | self.show() 171 | 172 | def poweroff(self): 173 | self.write_cmd(SET_DISP | 0x00) 174 | 175 | def poweron(self): 176 | self.write_cmd(SET_DISP | 0x01) 177 | 178 | def contrast(self, contrast): 179 | self.write_cmd(SET_CONTRAST) 180 | self.write_cmd(contrast) 181 | 182 | def invert(self, invert): 183 | self.write_cmd(SET_NORM_INV | (invert & 1)) 184 | 185 | def show(self): 186 | x0 = 0 187 | x1 = self.width - 1 188 | if self.width == 64: 189 | # displays with width of 64 pixels are shifted by 32 190 | x0 += 32 191 | x1 += 32 192 | self.write_cmd(SET_COL_ADDR) 193 | self.write_cmd(x0) 194 | self.write_cmd(x1) 195 | self.write_cmd(SET_PAGE_ADDR) 196 | self.write_cmd(0) 197 | self.write_cmd(self.pages - 1) 198 | self.write_data(self.buffer) 199 | 200 | 201 | class SSD1306_I2C(SSD1306): 202 | def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False): 203 | self.i2c = i2c 204 | self.addr = addr 205 | self.temp = bytearray(2) 206 | self.write_list = [b'\x40', None] # Co=0, D/C#=1 207 | super().__init__(width, height, external_vcc) 208 | 209 | def write_cmd(self, cmd): 210 | self.temp[0] = 0x80 # Co=1, D/C#=0 211 | self.temp[1] = cmd 212 | self.i2c.writeto(self.addr, self.temp) 213 | 214 | def write_data(self, buf): 215 | self.write_list[1] = buf 216 | self.i2c.writevto(self.addr, self.write_list) 217 | 218 | 219 | class SSD1306_SPI(SSD1306): 220 | def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): 221 | self.rate = 10 * 1024 * 1024 222 | dc.init(dc.OUT, value=0) 223 | res.init(res.OUT, value=0) 224 | cs.init(cs.OUT, value=1) 225 | self.spi = spi 226 | self.dc = dc 227 | self.res = res 228 | self.cs = cs 229 | import time 230 | self.res(1) 231 | time.sleep_ms(1) 232 | self.res(0) 233 | time.sleep_ms(10) 234 | self.res(1) 235 | super().__init__(width, height, external_vcc) 236 | 237 | def write_cmd(self, cmd): 238 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 239 | self.cs(1) 240 | self.dc(0) 241 | self.cs(0) 242 | self.spi.write(bytearray([cmd])) 243 | self.cs(1) 244 | 245 | def write_data(self, buf): 246 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 247 | self.cs(1) 248 | self.dc(1) 249 | self.cs(0) 250 | self.spi.write(buf) 251 | self.cs(1) 252 | 253 | class gameESP(): 254 | max_vol = 6 255 | duty={0:0,1:0.05,2:0.1,3:0.5,4:1,5:2,6:70} 256 | tones = { 257 | 'c4': 262, 258 | 'd4': 294, 259 | 'e4': 330, 260 | 'f4': 349, 261 | 'f#4': 370, 262 | 'g4': 392, 263 | 'g#4': 415, 264 | 'a4': 440, 265 | "a#4": 466, 266 | 'b4': 494, 267 | 'c5': 523, 268 | 'c#5': 554, 269 | 'd5': 587, 270 | 'd#5': 622, 271 | 'e5': 659, 272 | 'f5': 698, 273 | 'f#5': 740, 274 | 'g5': 784, 275 | 'g#5': 831, 276 | 'a5': 880, 277 | 'b5': 988, 278 | 'c6': 1047, 279 | 'c#6': 1109, 280 | 'd6': 1175, 281 | ' ': 0 282 | } 283 | 284 | def __init__(self): 285 | # True = SPI display, False = I2C display 286 | self.ESP32 = True 287 | self.paddle2 = False 288 | self.useSPI = True 289 | self.timer = 0 290 | self.vol = int(self.max_vol/2) + 1 291 | seed(ticks_us()) 292 | self.btnU = 1 << 1 293 | self.btnL = 1 << 2 294 | self.btnR = 1 << 3 295 | self.btnD = 1 << 4 296 | self.btnA = 1 << 5 297 | self.btnB = 1 << 6 298 | self.btnUval = 0 299 | self.btnDval = 0 300 | self.btnLval = 0 301 | self.btnRval = 0 302 | self.btnAval = 0 303 | self.btnBval = 0 304 | self.frameRate = 30 305 | self.screenW = 128 306 | self.screenH = 64 307 | self.Btns = 0 308 | self.lastBtns = 0 309 | 310 | self.PinBuzzer = Pin(26, Pin.OUT) 311 | 312 | # configure oled display SPI SSD1306 313 | self.spi = SPI(2, baudrate=14500000, sck=Pin(18), mosi=Pin(23), miso=Pin(19)) 314 | # display = Display(spi, rst=Pin(4), dc=Pin(21), cs=Pin(5), ) 315 | #DC, RES, CS 316 | self.display = SSD1306_SPI(128, 64, self.spi, Pin(21), Pin(4), Pin(5)) 317 | 318 | self.PinBtnA = Pin(32, Pin.IN, Pin.PULL_UP) 319 | self.PinBtnB = Pin(33, Pin.IN, Pin.PULL_UP) 320 | self.adcX = ADC(34) 321 | self.adcY = ADC(35) 322 | self.adc = ADC(39) 323 | self.adcX.atten(ADC.ATTN_11DB) 324 | self.adcY.atten(ADC.ATTN_11DB) 325 | self.adc.atten(ADC.ATTN_11DB) 326 | 327 | 328 | def deinit(self) : 329 | self.adc.deinit() 330 | self.adcX.deinit() 331 | self.adcY.deinit() 332 | if self.useSPI : 333 | self.spi.deinit() 334 | 335 | def getPaddle (self) : 336 | # ESP32 - 142 to 3155 337 | return max ( min (int (self.adc.read() / 2.935) - 48, 1023),0) 338 | 339 | def pressed (self,btn) : 340 | return (self.Btns & btn) 341 | 342 | def justPressed (self,btn) : 343 | return (self.Btns & btn) and not (self.lastBtns & btn) 344 | 345 | def justReleased (self,btn) : 346 | return (self.lastBtns & btn) and not (self.Btns & btn) 347 | 348 | def getBtn(self) : 349 | 350 | self.btnAval = not self.PinBtnA.value() 351 | self.btnBval = not self.PinBtnB.value() 352 | 353 | val = self.adcX.read() 354 | self.btnLval = 1 if val > 2500 else 0 355 | self.btnRval = 1 if 1500 < val < 2000 else 0 356 | 357 | val = self.adcY.read() 358 | self.btnUval = 1 if val > 2500 else 0 359 | self.btnDval = 1 if 1500 < val < 2000 else 0 360 | 361 | self.lastBtns = self.Btns 362 | self.Btns = 0 363 | self.Btns = self.Btns | self.btnUval << 1 | self.btnLval << 2 | self.btnRval << 3 | self.btnDval << 4 | self.btnAval << 5 | self.btnBval << 6 364 | return self.Btns 365 | print (self.Btns) 366 | 367 | def setVol(self) : 368 | if self.pressed(self.btnB): 369 | if self.justPressed(self.btnU) : 370 | self.vol= min (self.vol+1, self.max_vol) 371 | self.playTone('c4', 100) 372 | return True 373 | elif self.justPressed(self.btnD) : 374 | self.vol= max (self.vol-1, 0) 375 | self.playTone('d4', 100) 376 | return True 377 | 378 | return False 379 | 380 | def playTone(self, tone, tone_duration, rest_duration=0): 381 | beeper = PWM(self.PinBuzzer, freq=self.tones[tone], duty=self.duty[self.vol]) 382 | sleep_ms(tone_duration) 383 | beeper.deinit() 384 | sleep_ms(rest_duration) 385 | 386 | def playSound(self, freq, tone_duration, rest_duration=0): 387 | beeper = PWM(self.PinBuzzer, freq, duty=self.duty[self.vol]) 388 | sleep_ms(tone_duration) 389 | beeper.deinit() 390 | sleep_ms(rest_duration) 391 | 392 | def random (self, x, y) : 393 | return getrandbits(20) % (y-x+1) + x 394 | 395 | def display_and_wait(self) : 396 | self.display.show() 397 | timer_dif = int(1000/self.frameRate) - ticks_diff(ticks_ms(), self.timer) 398 | if timer_dif > 0 : 399 | sleep_ms(timer_dif) 400 | self.timer=ticks_ms() 401 | 402 | 403 | class Rect (object): 404 | def __init__(self, x, y, w, h): 405 | self.x = x 406 | self.y = y 407 | self.w = w 408 | self.h = h 409 | 410 | 411 | def move (self, vx, vy) : 412 | self.x = self.x + vx 413 | self.y = self.y + vy 414 | 415 | 416 | def colliderect (self, rect1) : 417 | if (self.x + self.w > rect1.x and 418 | self.x < rect1.x + rect1.w and 419 | self.y + self.h > rect1.y and 420 | self.y < rect1.y + rect1.h) : 421 | return True 422 | else: 423 | return False 424 | -------------------------------------------------------------------------------- /tetris.py: -------------------------------------------------------------------------------- 1 | # tetris.py 2 | # 3 | # ESP8266 (node MCU D1 mini) micropython game for Tetris 4 | # by Billy Cheung 2019 08 31 5 | # 6 | # SPI OLED 7 | # GND 8 | # VCC 9 | # D0/Sck - D5 (=GPIO14=HSCLK) 10 | # D1/MOSI- D7 (=GPIO13=HMOSI) 11 | # RES - D0 (=GPIO16) 12 | # DC - D4 (=GPIO2) 13 | # CS - D3 (=GPIO0) 14 | # Speaker 15 | # GPIO15  D8 Speaker 16 | # n.c. - D6 (=GPIO13=HMOSI) 17 | # 18 | # GPIO5   D1——   On to read ADC for Btn 19 | # GPIO4   D2——   On to read ADC for Paddle 20 | # 21 | # buttons A0 22 | # A0 VCC-9K-U-9K-L-12K-R-9K-D-9K-A-12K-B-9K-GND 23 | # 24 | import gc 25 | import sys 26 | gc.collect() 27 | import utime 28 | from utime import sleep_ms 29 | import network 30 | from math import sqrt 31 | # all dislplay, buttons, paddle, sound logics are in gameESP.mpy module 32 | from gameESP import * 33 | g=gameESP() 34 | 35 | g.frameRate = 30 36 | g.bgm = 3 37 | g.maxBgm = 3 38 | bgmBuf= [ 39 | [g.songStart, False, 1, g.songEnd], 40 | # Empire Strikes Back 41 | [ g.songStart,True, 100, 0, 4, 42 | 'g3',1,0,1,'g3',1,0,1,'g3',1,0,1,'c4',8,'g4',8,0,4,'f4',2,'e4',2,'d4',2,'c5',8,'g4',8, 0,4, 'f4',2,'e4',2,'d4',2,'c5',8,'g4',8,0,4,'f4',2,'e4',2,'f4',2,'d4',8,0,8, 43 | 'g3',1,0,1,'g3',1,0,1,'g3',1,0,1,'c4',8,'g4',8,0,4,'f4',2,'e4',2,'d4',2,'c5',8,'g4',8, 0,4, 'f4',2,'e4',2,'d4',2,'c5',8,'g4',8,0,4,'f4',2,'e4',2,'f4',2,'d4',8,0,8, 44 | 'g3',1,0,1,'g3',1,0,1,'a3',4,0,4,'f4',2,'e4',2,'d4',2,'c4',1,0,1,'c4',2,'d4',1,'e4',1,'d4',2,'a3',2,'b3',4, 45 | 'g3',1,0,1,'g3',1,0,1,'a3',4,0,4,'f4',2,'e4',2,'d4',2,'c4',1,0,1,'g4',2,0,1,'d4',1,'d4',4,0,4, 46 | 'g3',1,0,1,'g3',1,0,1,'a3',4,0,4,'f4',2,'e4',2,'d4',2,'c4',1,0,1,'c4',2,'d4',1,'e4',1,'d4',2,'a3',2,'b3',4, 47 | 'e4',1,0,1,'e4',2,'a4',2,'g4',2,'f4',2,'e4',2,'d4',2,'c4',2,'b3',2,'a3',2,'e4',8, 0, 8, 48 | g.songLoop], 49 | # The Imperial March 50 | [ g.songStart,False, 1, 0, 400, 51 | 440, 400, 0, 100, 440, 400, 0, 100, 440, 400, 0,100, 349, 350, 523, 150, 440, 500, 349, 350, 523, 150, 440, 650, 0,500, 659, 500, 659, 500, 659, 500, 698, 350, 523, 150, 415, 500, 349, 350, 523, 150, 440, 650, 0, 500, 52 | 880, 500, 440, 300, 440, 150, 880, 500, 830, 325, 784, 175, 740, 125, 698, 125, 740, 250, 0, 325, 445, 250, 622, 500, 587, 325, 554, 175, 523, 125, 466, 125, 523, 250, 0, 350, 53 | 349, 250, 415, 500, 349, 350, 440, 125, 523, 500, 440, 375, 523, 125, 659, 650, 0, 500,349, 250, 415, 500, 349, 375, 523, 125, 440, 500, 349, 375, 523, 125, 440, 650,0, 650, 54 | 880, 500, 440, 300, 440, 150, 880, 500, 830, 325, 784, 175, 740, 125, 698, 125, 740, 250, 0, 325, 445, 250, 622, 500, 587, 325, 554, 175, 523, 125, 466, 125, 523, 250, 0, 350, 55 | 349, 250, 415, 500, 349, 350, 440, 125, 523, 500, 440, 375, 523, 125, 659, 650, 0, 500,349, 250, 415, 500, 349, 375, 523, 125, 440, 500, 349, 375, 523, 125, 440, 650,0, 650, 56 | g.songLoop], 57 | # Tetris 58 | [ g.songStart,False,200, 0, 4, 59 | 659,2, 494, 1, 523,1, 587,2, 523, 1, 493, 1, 440, 2, 440, 1, 523,1, 659,2,587,1,523,1,493,2, 493,1,523,1,587,2,659,2,523,2,440,2,440,2,0,2,587, 1,698,1,880,2,783,1,698,1,659,2,523,1,659,2,587,1,523,1,493,2,493,1,523,1,587,2,659,2,523,2,440,2,440,2,0,2, 60 | 329,4,261,4,293,4,246,4,261,4,220,4,207,4,246,4,329,4,261,4,293,4,246,4,261,2,329,2,440,4,415,6,0,2, 61 | g.songLoop] 62 | ] 63 | 64 | # size = width, height = 200, 400 65 | size = width, height = 30, 60 66 | # color = {'black': (0, 0, 0), 'white':(255, 255, 255)} 67 | color ={'black': 0, 'white':1} 68 | sqrsize = 3 69 | occupied_squares = [] 70 | top_of_screen = (2, 2) 71 | top_x, top_y = top_of_screen[0], top_of_screen[1] 72 | num_block = 4 73 | pen_size = 1 74 | mov_delay, r_delay = 200, 50 75 | board_centre = int(width/2)+2 76 | no_move = 0 77 | score = 0 78 | life = 0 79 | shape_blcks = [] 80 | shape_name = "" 81 | new_shape_blcks = [] 82 | new_shape_name = "" 83 | occupied_squares = [] 84 | 85 | def reset_board(): 86 | global shape_blcks, shape_name, occupied_squares 87 | shape_blcks = [] 88 | shape_name = "" 89 | occupied_squares = [] 90 | g.display.fill(0) 91 | g.display.rect(top_x-1, top_y-1, width+2, height+2,1) 92 | 93 | def drawScore () : 94 | global score, life 95 | g.display.text('S:{}'.format (score), 40,0,1) 96 | g.display.text('L:{}'.format (life), 90,0,1) 97 | 98 | def draw_shape(): 99 | '''this draws list of blocks or a block to the background and blits 100 | background to screen''' 101 | 102 | if isinstance(shape_blcks,list): 103 | for blck in shape_blcks: 104 | g.display.rect(blck[0], blck[1], sqrsize, sqrsize, 1) 105 | else: 106 | g.display.rect(shape_blcks[0], shape_blcks[1], sqrsize, sqrsize,1) 107 | 108 | def row_filled(row_no): 109 | global occupied_squares 110 | '''check if a row is fully occupied by a shape block''' 111 | 112 | for x_coord in range(top_x, width+top_x, sqrsize): 113 | 114 | if (x_coord, row_no) in occupied_squares: 115 | continue 116 | else: 117 | return False 118 | return True 119 | 120 | 121 | def delete_row(row_no): 122 | '''removes all squares on a row from the occupied_squares list and then 123 | moves all square positions which have y-axis coord less than row_no down 124 | board''' 125 | global occupied_squares 126 | g.display.fill(0) 127 | g.display.rect(top_x-1, top_y-1, width+2, height+2,1) 128 | new_buffer = [] 129 | x_coord, y_coord = 0, 1 130 | for sqr in occupied_squares: 131 | if sqr[y_coord] != row_no: 132 | new_buffer.append(sqr) 133 | occupied_squares = new_buffer 134 | for index in range(len(occupied_squares)): 135 | if occupied_squares[index][y_coord] < row_no: 136 | occupied_squares[index] = (occupied_squares[index][x_coord], 137 | occupied_squares[index][y_coord] + sqrsize) 138 | for sqr in occupied_squares: 139 | g.display.rect(sqr[x_coord], sqr[y_coord], sqrsize, sqrsize, 1) 140 | 141 | def move(direction): 142 | global shape_blcks 143 | '''input:- list of blocks making up a tetris shape 144 | output:- list of blocks making up a tetris shape 145 | function moves the input list of blocks that make up shape and then checks 146 | that the list of blocks are all in positions that are valide. position is 147 | valid if it has not been occupied previously and is within the tetris board. 148 | If move is successful, function returns the moved shape and if move is not 149 | possible, function returns a false''' 150 | directs = {'down':(no_move, sqrsize), 'left':(-sqrsize, no_move), 151 | 'right':(sqrsize, no_move), 'pause': (no_move, no_move)} 152 | delta_x, delta_y = directs[direction] 153 | 154 | for index in range(num_block): 155 | shape_blcks[index] = [shape_blcks[index][0] + delta_x, shape_blcks[index][1]+ delta_y] 156 | 157 | if legal(shape_blcks): 158 | for index in range(num_block): 159 | #erase previous positions of block 160 | g.display.fill_rect(shape_blcks[index][0]-delta_x, shape_blcks[index][1]-delta_y, sqrsize, sqrsize, 0) 161 | return True 162 | else: 163 | # undo the move, as it's not legal (being blocked by existing blocks) 164 | for index in range(num_block): 165 | shape_blcks[index] = [shape_blcks[index][0]-delta_x, shape_blcks[index][1]- delta_y] 166 | return False 167 | 168 | 169 | def legal(blcks): 170 | '''input: list of shape blocks 171 | checks whether a shape is in a legal portion of the board as defined in the 172 | doc of 'move' function''' 173 | 174 | for index in range(num_block): 175 | new_x = blcks[index][0] 176 | new_y = blcks[index][1] 177 | if (((new_x, new_y) in occupied_squares or new_y >= height) or 178 | (new_x >= width or new_x < top_x)): 179 | return False 180 | 181 | return True 182 | 183 | 184 | def create_newshape(start_x=board_centre, start_y=2): 185 | '''A shape is a list of four rectangular blocks. 186 | Input:- coordinates of board at which shape is to be created 187 | Output:- a list of the list of the coordinates of constituent blocks of each 188 | shape relative to a reference block and shape name. Reference block has 189 | starting coordinates of start_x and start_y. ''' 190 | global shape_blcks, shape_name, new_shape_blcks, new_shape_name 191 | shape_blcks = new_shape_blcks 192 | shape_name = new_shape_name 193 | 194 | shape_names = ['S', 'O', 'I', 'L', 'T'] 195 | shapes = {'S':[(start_x + 1*sqrsize, start_y + 2*sqrsize), 196 | (start_x, start_y), (start_x, start_y + 1*sqrsize),(start_x + 1*sqrsize, 197 | start_y + 1*sqrsize)], 198 | 199 | 'O':[(start_x + 1*sqrsize, start_y + 1*sqrsize), (start_x, start_y), 200 | (start_x, start_y + 1*sqrsize), (start_x + 1*sqrsize, start_y)], 201 | 202 | 'I':[(start_x, start_y + 3*sqrsize), (start_x, start_y), 203 | (start_x, start_y + 2*sqrsize), (start_x, start_y + 1*sqrsize)], 204 | 205 | 'L':[(start_x + 1*sqrsize, start_y + 2*sqrsize), (start_x, start_y), 206 | (start_x, start_y + 2*sqrsize), (start_x, start_y + 1*sqrsize)], 207 | 208 | 'T':[(start_x + 1*sqrsize, start_y + 1*sqrsize),(start_x, start_y), 209 | (start_x - 1*sqrsize, start_y + 1*sqrsize),(start_x, 210 | start_y + 1*sqrsize)] 211 | } 212 | a_shape = g.random(0, 4) 213 | new_shape_blcks = shapes[shape_names[a_shape]] 214 | new_shape_name = shape_names[a_shape] 215 | 216 | g.display.fill_rect(40, top_y+15, 60, 40,0 ) 217 | if isinstance(new_shape_blcks, list): 218 | for blck in new_shape_blcks: 219 | g.display.rect(blck[0]+40, blck[1]+15, sqrsize, sqrsize, 1) 220 | else: 221 | g.display.rect(new_shape_blcks[0+40], new_shape_blcks[1]+15, sqrsize, sqrsize,1) 222 | 223 | def rotate(): 224 | '''input:- list of shape blocks 225 | ouput:- list of shape blocks 226 | function tries to rotate ie change orientation of blocks in the shape 227 | and this applied depending on the shape for example if a 'O' shape is passed 228 | to this function, the same shape is returned because the orientation of such 229 | shape cannot be changed according to tetris rules''' 230 | if shape_name == 'O': 231 | return shape_blcks 232 | else: 233 | #global no_move, occupied_squares, background 234 | 235 | ref_shape_ind = 3 # index of block along which shape is rotated 236 | start_x, start_y = (shape_blcks[ref_shape_ind][0], 237 | shape_blcks[ref_shape_ind][1]) 238 | save_blcks = shape_blcks 239 | Rshape_blcks = [(start_x + start_y-shape_blcks[0][1], 240 | start_y - (start_x - shape_blcks[0][0])), 241 | (start_x + start_y-shape_blcks[1][1], 242 | start_y - (start_x - shape_blcks[1][0])), 243 | (start_x + start_y-shape_blcks[2][1], 244 | start_y - (start_x - shape_blcks[2][0])), 245 | (shape_blcks[3][0], shape_blcks[3][1])] 246 | 247 | if legal(Rshape_blcks): 248 | for index in range(num_block): # erase the previous shape 249 | g.display.fill_rect(shape_blcks[index][0], shape_blcks[index][1],sqrsize, sqrsize, 0) 250 | return Rshape_blcks 251 | else: 252 | return shape_blcks 253 | 254 | exitGame = False 255 | demo = False 256 | while not exitGame: 257 | 258 | g.startSong(bgmBuf[g.bgm]) 259 | 260 | #menu screen 261 | while True: 262 | g.display.fill(0) 263 | g.display.text('Tetris', 0, 0, 1) 264 | g.display.rect(90,0, g.max_vol*4+2,6,1) 265 | g.display.fill_rect(91,1, g.vol * 4,4,1) 266 | g.display.text('A Start B+L Quit', 0, 10, 1) 267 | if demo : 268 | g.display.text('D AI-Player', 0,20, 1) 269 | else : 270 | g.display.text('D 1-Player', 0,20, 1) 271 | g.display.text('R Frame/s {}'.format(g.frameRate), 0,30, 1) 272 | if g.bgm : 273 | g.display.text('L Music {}'.format(g.bgm), 0, 40, 1) 274 | else : 275 | g.display.text('L Music Off', 0, 40, 1) 276 | g.display.text('B + U/D Loudness', 0, 50, 1) 277 | g.display.show() 278 | 279 | g.getBtn() 280 | if g.setVol() : 281 | pass 282 | elif g.setFrameRate() : 283 | pass 284 | elif g.pressed(g.btnB) and g.justPressed(g.btnL) : 285 | exitGame = True 286 | gameOver= True 287 | break 288 | elif g.justPressed(g.btnA) : 289 | if demo : 290 | g.display.fill(0) 291 | g.display.text('DEMO', 5, 0, 1) 292 | g.display.text('B to Stop', 5, 30, 1) 293 | g.display.show() 294 | sleep_ms(1000) 295 | break 296 | elif g.justPressed(g.btnD) : 297 | demo = not demo 298 | elif g.justPressed(g.btnL) : 299 | g.bgm = 0 if g.bgm >= g.maxBgm else g.bgm + 1 300 | if g.bgm : 301 | g.startSong(bgmBuf[g.bgm]) 302 | else : 303 | g.stopSong() 304 | sleep_ms(10) 305 | 306 | 307 | life = 3 308 | Score = 0 309 | reset_board() 310 | create_newshape() 311 | gameOver = False 312 | # game loop 313 | while not gameOver: 314 | drawScore() 315 | create_newshape() 316 | extramoves = 3 317 | l_of_blcks_ind = blck_x_axis = 0 318 | shape_name_ind = blck_y_axis = 1 319 | 320 | move_dir = 'down' #default move direction 321 | game = 'playing' #default game state play:- is game paused or playing 322 | 323 | if legal(shape_blcks): 324 | draw_shape() 325 | else: 326 | life -= 1 327 | if life <= 0 : 328 | gameOver = True 329 | break 330 | else : 331 | g.playTone('g4', 100) 332 | g.playTone('e4', 100) 333 | g.playTone('c4', 100) 334 | g.display.show() 335 | sleep_ms(2000) 336 | reset_board() 337 | g.bgm = 0 if g.bgm >= g.maxBgm else g.bgm + 1 338 | if g.bgm : 339 | g.startSong(bgmBuf[g.bgm]) 340 | continue 341 | 342 | while True: 343 | mov_delay = 150 344 | move_dir = 'down' 345 | g.getBtn() 346 | if game == 'paused': 347 | if g.justPressed(g.btnB) : 348 | g.playTone('c4', 100) 349 | g.playTone('e4', 100) 350 | game = 'playing' 351 | else: 352 | if g.justPressed(g.btnB) : 353 | g.playTone('e4', 100) 354 | g.playTone('f4', 100) 355 | game = 'paused' 356 | move_dir = 'pause' 357 | elif g.pressed(g.btnU) and g.pressed(g.btnD) : 358 | gameOver = True 359 | break 360 | elif g.pressed(g.btnD) : 361 | mov_delay = 10 362 | move_dir = 'down' 363 | elif g.justPressed(g.btnA | g.btnU) : 364 | shape_blcks = rotate() 365 | draw_shape() 366 | g.display.show() 367 | sleep_ms(r_delay) 368 | continue 369 | elif g.pressed(g.btnL | g.btnR): 370 | move_dir = 'left' if g.pressed(g.btnL) else 'right' 371 | mov_delay = 50 372 | move (move_dir) 373 | draw_shape() 374 | g.display.show() 375 | sleep_ms(mov_delay) 376 | continue 377 | 378 | moved = move( move_dir) 379 | draw_shape() 380 | sleep_ms(mov_delay) 381 | 382 | '''if block did not move and the direction for movement is down 383 | then shape has come to rest so we can exit loop and then a new 384 | shape is generated. if direction for movement is sideways and 385 | block did not move it should be moved down rather''' 386 | if not moved and move_dir == 'down': 387 | extramoves = extramoves - 1 388 | if extramoves <= 0 : 389 | for block in shape_blcks: 390 | occupied_squares.append((block[0],block[1])) 391 | break 392 | 393 | 394 | draw_shape() 395 | g.display.show() 396 | 397 | for row_no in range (height - sqrsize + top_y, 0, -sqrsize): 398 | if row_filled(row_no): 399 | delete_row(row_no) 400 | score+=10 401 | drawScore() 402 | g.display.show() 403 | g.playTone('c4', 100) 404 | g.playTone('e4', 100) 405 | g.playTone('g4', 100) 406 | g.playTone('e4', 100) 407 | g.playTone('c4', 100) 408 | g.display.show() 409 | 410 | if gameOver : 411 | g.display.fill_rect(20,20, 80, 35,0) 412 | g.display.text("Game Over", 30,30,1) 413 | g.display.show() 414 | g.playTone('c4', 100) 415 | g.playTone('e4', 100) 416 | g.playTone('g4', 100) 417 | sleep_ms(2000) 418 | if g.ESP32 : 419 | g.deinit() 420 | del sys.modules["gameESP"] 421 | gc.collect() 422 | -------------------------------------------------------------------------------- /utils/lcd/tt24.py: -------------------------------------------------------------------------------- 1 | # Code generated by font-to-py.py. 2 | # Font: CM Sans Serif 2012.ttf 3 | version = '0.2' 4 | 5 | def height(): 6 | return 24 7 | 8 | def max_width(): 9 | return 20 10 | 11 | def hmap(): 12 | return False 13 | 14 | def reverse(): 15 | return False 16 | 17 | def monospaced(): 18 | return False 19 | 20 | def min_ch(): 21 | return 32 22 | 23 | def max_ch(): 24 | return 126 25 | 26 | _font =\ 27 | b'\x0c\x00\xc0\x00\x00\xf0\x00\x00\xf0\x00\x00\x78\x70\x07\x38\x78'\ 28 | b'\x07\x38\x7c\x07\x78\x1e\x00\xf0\x0f\x00\xf0\x07\x00\xc0\x03\x00'\ 29 | b'\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 30 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00'\ 31 | b'\x00\x02\xf8\x3f\x07\xf8\x3f\x07\xf8\x3f\x02\x00\x00\x00\x00\x00'\ 32 | b'\x00\x08\x00\xf8\x03\x00\xf8\x03\x00\x08\x00\x00\x00\x00\x00\xf8'\ 33 | b'\x03\x00\xf8\x03\x00\x08\x00\x00\x00\x00\x00\x0c\x00\x00\xe0\x00'\ 34 | b'\x80\xe3\x00\x80\xf3\x03\xe0\xff\x03\xe0\xff\x00\xe0\xe3\x00\x80'\ 35 | b'\xe3\x03\x80\xff\x03\xe0\xff\x01\xe0\xe7\x00\x80\xe3\x00\x80\x03'\ 36 | b'\x00\x0b\x00\x00\xe0\x00\xc0\xe3\x01\xe0\xc7\x01\x70\x8e\x03\xf8'\ 37 | b'\xef\x07\xf8\xff\x07\x70\x8c\x03\xe0\x9d\x01\xc0\xf9\x01\x00\xf0'\ 38 | b'\x00\x00\x00\x00\x12\x00\xe0\x01\x00\xf0\x03\x00\x38\x07\x00\x38'\ 39 | b'\x07\x04\x38\x07\x07\x38\xc7\x07\xf0\xf3\x01\xe0\x79\x00\x00\x1e'\ 40 | b'\x00\x80\x07\x00\xe0\xe3\x01\xf8\xf0\x03\x38\x38\x07\x08\x38\x07'\ 41 | b'\x00\x38\x07\x00\xf0\x03\x00\xe0\x01\x00\x00\x00\x0f\x00\x00\xf0'\ 42 | b'\x00\x00\xf8\x01\xe0\xfd\x03\xf0\x8f\x07\xf8\x07\x07\x38\x0e\x07'\ 43 | b'\x38\x1f\x07\xf8\x3f\x07\xf0\xfb\x07\xe0\xf0\x07\x00\xe0\x03\x00'\ 44 | b'\xf8\x07\x00\xf8\x07\x00\x78\x06\x00\x00\x04\x04\x00\xf8\x03\x00'\ 45 | b'\xf8\x03\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x7f\x00\xe0\xff'\ 46 | b'\x03\xf8\xff\x0f\x7c\x00\x1f\x0e\x00\x38\x02\x00\x20\x02\x00\x20'\ 47 | b'\x07\x00\x02\x00\x20\x0e\x00\x38\x7c\x00\x1f\xf8\xff\x0f\xe0\xff'\ 48 | b'\x03\x00\x7f\x00\x00\x00\x00\x09\x00\x60\x00\x00\x60\x00\x00\x78'\ 49 | b'\x03\x00\xf0\x01\x00\xe0\x00\x00\xf8\x03\x00\x68\x01\x00\x60\x00'\ 50 | b'\x00\x00\x00\x00\x0c\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ 51 | b'\x38\x00\x80\xff\x03\x80\xff\x03\x80\xff\x03\x00\x38\x00\x00\x38'\ 52 | b'\x00\x00\x38\x00\x00\x38\x00\x00\x00\x00\x05\x00\x00\x00\x37\x00'\ 53 | b'\x00\x3f\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x07\x00\x00\x38\x00'\ 54 | b'\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ 55 | b'\x38\x00\x05\x00\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x00'\ 56 | b'\x00\x00\x00\x08\x00\x00\x00\x06\x00\xe0\x07\x00\xfc\x07\xc0\xff'\ 57 | b'\x01\xf8\x3f\x00\xfc\x03\x00\x7c\x00\x00\x04\x00\x00\x0d\x00\x80'\ 58 | b'\x7f\x00\xe0\xff\x01\xf0\xff\x03\xf8\xc0\x07\x38\x00\x07\x38\x00'\ 59 | b'\x07\x38\x00\x07\xf8\xc0\x07\xf0\xff\x03\xe0\xff\x01\x80\x7f\x00'\ 60 | b'\x00\x00\x00\x00\x00\x00\x08\x00\xc0\x01\x00\xc0\x01\x00\xe0\x01'\ 61 | b'\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00\x00'\ 62 | b'\x0d\x00\xc0\x81\x07\xe0\xe1\x07\xf0\xe1\x07\x78\x70\x07\x38\x38'\ 63 | b'\x07\x38\x38\x07\x38\x1c\x07\x78\x1e\x07\xf0\x0f\x07\xf0\x07\x07'\ 64 | b'\xc0\x03\x07\x00\x00\x00\x00\x00\x00\x0c\x00\xc0\xc0\x00\xf0\xc0'\ 65 | b'\x03\xf0\xc0\x03\x78\x80\x07\x38\x00\x07\x38\x0e\x07\x38\x0e\x07'\ 66 | b'\x78\x9e\x07\xf0\xff\x03\xf0\xff\x03\xe0\xf1\x00\x00\x00\x00\x0c'\ 67 | b'\x00\x00\xf0\x00\x00\xf8\x00\x00\xfe\x00\x00\xef\x00\xc0\xe7\x00'\ 68 | b'\xf0\xe1\x00\xf8\xe0\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00'\ 69 | b'\xe0\x00\x00\xe0\x00\x0c\x00\x00\xce\x00\xf8\xcf\x03\xf8\xcf\x03'\ 70 | b'\x38\x8e\x07\x38\x07\x07\x38\x07\x07\x38\x07\x07\x38\x8f\x07\x38'\ 71 | b'\xfe\x03\x38\xfc\x01\x00\xf8\x00\x00\x00\x00\x0d\x00\x00\x7f\x00'\ 72 | b'\xe0\xff\x01\xf0\xff\x03\x78\x8e\x07\x38\x07\x07\x38\x07\x07\x38'\ 73 | b'\x07\x07\x78\x8f\x07\xf0\xfe\x03\xe0\xfc\x01\xc0\xf8\x00\x00\x00'\ 74 | b'\x00\x00\x00\x00\x0b\x00\x38\x00\x00\x38\x00\x00\x38\xc0\x07\x38'\ 75 | b'\xf8\x07\x38\xfe\x07\xb8\x3f\x00\xf8\x07\x00\xf8\x01\x00\xf8\x00'\ 76 | b'\x00\x78\x00\x00\x38\x00\x00\x0c\x00\x00\xf0\x00\xe0\xf9\x01\xf0'\ 77 | b'\xff\x03\xf0\x9f\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e'\ 78 | b'\x07\xf0\x9f\x07\xf0\xff\x03\xe0\xf9\x01\x00\xf0\x00\x0d\x00\xc0'\ 79 | b'\x07\x00\xe0\x0f\x01\xf0\x1f\x03\x78\x3c\x07\x38\x38\x07\x38\x38'\ 80 | b'\x07\x38\x38\x07\x38\x38\x07\x70\x9c\x07\xf0\xff\x03\xe0\xff\x01'\ 81 | b'\x80\x7f\x00\x00\x00\x00\x05\x00\x00\x07\x07\x00\x07\x07\x00\x07'\ 82 | b'\x07\x00\x00\x00\x00\x00\x00\x05\x00\x00\x07\x37\x00\x07\x3f\x00'\ 83 | b'\x07\x1f\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x38\x00\x00\x38\x00'\ 84 | b'\x00\x7c\x00\x00\x6c\x00\x00\xee\x00\x00\xee\x00\x00\xc7\x01\x00'\ 85 | b'\xc7\x01\x80\x83\x03\x00\x00\x00\x0b\x00\x00\xce\x01\x00\xce\x01'\ 86 | b'\x00\xce\x01\x00\xce\x01\x00\xce\x01\x00\xce\x01\x00\xce\x01\x00'\ 87 | b'\xce\x01\x00\xce\x01\x00\xce\x01\x00\x00\x00\x0a\x00\x80\x83\x03'\ 88 | b'\x80\x83\x03\x00\xc7\x01\x00\xc7\x01\x00\xee\x00\x00\xee\x00\x00'\ 89 | b'\x7c\x00\x00\x7c\x00\x00\x38\x00\x00\x00\x00\x0c\x00\xc0\x00\x00'\ 90 | b'\xf0\x00\x00\xf0\x00\x00\x78\x70\x07\x38\x78\x07\x38\x7c\x07\x78'\ 91 | b'\x1e\x00\xf0\x0f\x00\xf0\x07\x00\xc0\x03\x00\x00\x00\x00\x00\x00'\ 92 | b'\x00\x11\x00\x00\x3f\x00\xc0\xff\x00\xe0\xe1\x01\xf0\xc0\x03\x70'\ 93 | b'\xbc\x03\x78\x7e\x07\x38\x7f\x07\xb8\x73\x07\xb8\x33\x07\xb8\x3f'\ 94 | b'\x07\x38\x7f\x07\x70\xf3\x03\x70\x70\x03\xe0\x38\x02\xe0\x3f\x00'\ 95 | b'\x80\x0f\x00\x00\x00\x00\x0f\x00\x00\x00\x06\x00\xc0\x07\x00\xf0'\ 96 | b'\x07\x00\xfe\x03\xc0\xff\x00\xf8\xef\x00\xf8\xe1\x00\x78\xe0\x00'\ 97 | b'\xf8\xe3\x00\xf0\xff\x00\xc0\xff\x00\x00\xfe\x07\x00\xf0\x07\x00'\ 98 | b'\x80\x07\x00\x00\x04\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07'\ 99 | b'\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38'\ 100 | b'\x9e\x07\xf8\xff\x07\xf0\xff\x03\xe0\xf1\x01\x00\x00\x00\x00\x00'\ 101 | b'\x00\x00\x00\x00\x10\x00\x00\x3f\x00\xc0\xff\x00\xe0\xff\x01\xf0'\ 102 | b'\xe1\x03\x70\x80\x03\x78\x80\x07\x38\x00\x07\x38\x00\x07\x38\x00'\ 103 | b'\x07\x38\x00\x07\x78\x80\x03\xf0\xc0\x03\xe0\xc0\x01\xc0\xc0\x00'\ 104 | b'\x80\x40\x00\x00\x00\x00\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff'\ 105 | b'\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07'\ 106 | b'\x78\x80\x07\xf0\xc0\x03\xe0\xff\x01\xc0\xff\x00\x00\x3f\x00\x00'\ 107 | b'\x00\x00\x00\x00\x00\x0e\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07'\ 108 | b'\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38'\ 109 | b'\x0e\x07\x38\x0e\x07\x38\x00\x07\x38\x00\x07\x00\x00\x00\x00\x00'\ 110 | b'\x00\x0d\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x38\x1c\x00\x38'\ 111 | b'\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c'\ 112 | b'\x00\x38\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x3f\x00\xc0'\ 113 | b'\xff\x00\xe0\xff\x01\xf0\xe1\x03\x70\x80\x03\x78\x80\x07\x38\x00'\ 114 | b'\x07\x38\x00\x07\x38\x1c\x07\x38\x1c\x07\x78\x9c\x03\xf0\xdc\x01'\ 115 | b'\xe0\xfc\x07\xc0\xfc\x07\x80\xfc\x07\x00\x00\x00\x00\x00\x00\x0f'\ 116 | b'\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x0e\x00\x00\x0e\x00'\ 117 | b'\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\xf8'\ 118 | b'\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00\x00\x06\x00'\ 119 | b'\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00\x00\x00'\ 120 | b'\x00\x00\x0c\x00\x00\xe0\x00\x00\xe0\x01\x00\xe0\x03\x00\x80\x07'\ 121 | b'\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x80\x07\xf8\xff\x03\xf8'\ 122 | b'\xff\x03\xf8\xff\x00\x00\x00\x00\x0e\x00\xf8\xff\x07\xf8\xff\x07'\ 123 | b'\xf8\xff\x07\x00\x1e\x00\x00\x0f\x00\x80\x1f\x00\xc0\x7f\x00\xe0'\ 124 | b'\xf9\x00\xf0\xf0\x03\x78\xc0\x07\x38\x80\x07\x18\x00\x07\x08\x00'\ 125 | b'\x04\x00\x00\x00\x0c\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00'\ 126 | b'\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00'\ 127 | b'\x07\x00\x00\x07\x00\x00\x07\x00\x00\x00\x12\x00\xf8\xff\x07\xf8'\ 128 | b'\xff\x07\xf8\xff\x07\xf8\x01\x00\xf8\x1f\x00\xc0\xff\x00\x00\xfc'\ 129 | b'\x07\x00\xe0\x07\x00\xe0\x07\x00\xfc\x07\xc0\xff\x00\xf8\x1f\x00'\ 130 | b'\xf8\x01\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00'\ 131 | b'\x00\x00\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\xf8\x00\x00'\ 132 | b'\xe0\x03\x00\xc0\x0f\x00\x00\x3f\x00\x00\xfc\x00\x00\xf0\x01\x00'\ 133 | b'\xe0\x07\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00'\ 134 | b'\x00\x11\x00\x00\x3f\x00\xc0\xff\x00\xe0\xff\x01\xf0\xe1\x03\x70'\ 135 | b'\x80\x03\x78\x80\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07\x78\x80'\ 136 | b'\x07\x70\x80\x03\xf0\xe1\x03\xe0\xff\x01\xc0\xff\x00\x00\x3f\x00'\ 137 | b'\x00\x00\x00\x00\x00\x00\x0e\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff'\ 138 | b'\x07\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00'\ 139 | b'\x78\x1e\x00\xf0\x0f\x00\xf0\x0f\x00\xc0\x03\x00\x00\x00\x00\x00'\ 140 | b'\x00\x00\x11\x00\x00\x3f\x00\xc0\xff\x00\xe0\xff\x01\xf0\xe1\x03'\ 141 | b'\x70\x80\x03\x78\x80\x07\x38\x00\x07\x38\x10\x07\x38\x38\x07\x38'\ 142 | b'\x70\x07\x78\xf0\x07\x70\xe0\x03\xf0\xe1\x03\xe0\xff\x07\xc0\xff'\ 143 | b'\x03\x00\x3f\x02\x00\x00\x00\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8'\ 144 | b'\xff\x07\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x3c\x00\x38\xfc'\ 145 | b'\x00\x38\xfc\x03\x78\xfe\x07\xf0\x9f\x07\xf0\x0f\x07\xc0\x07\x04'\ 146 | b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\xc0\x00\xe0\xc1\x01\xf0\xe3'\ 147 | b'\x03\xf0\x87\x03\x78\x06\x07\x38\x0e\x07\x38\x0e\x07\x38\x0c\x07'\ 148 | b'\x38\x0c\x07\x78\x9c\x07\xf0\xf8\x03\xe0\xf8\x03\xc0\xf0\x00\x00'\ 149 | b'\x00\x00\x0c\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ 150 | b'\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x38\x00\x00\x38\x00\x00\x38'\ 151 | b'\x00\x00\x38\x00\x00\x00\x00\x00\x0e\x00\xf8\xff\x00\xf8\xff\x01'\ 152 | b'\xf8\xff\x03\x00\x80\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00'\ 153 | b'\x00\x07\x00\x80\x07\xf8\xff\x03\xf8\xff\x01\xf8\xff\x00\x00\x00'\ 154 | b'\x00\x00\x00\x00\x0e\x00\x18\x00\x00\xf8\x00\x00\xf8\x07\x00\xf0'\ 155 | b'\x3f\x00\x00\xff\x01\x00\xf8\x07\x00\x80\x07\x00\xe0\x07\x00\xfe'\ 156 | b'\x07\xc0\x7f\x00\xf8\x0f\x00\xf8\x01\x00\x38\x00\x00\x08\x00\x00'\ 157 | b'\x14\x00\x18\x00\x00\xf8\x01\x00\xf8\x1f\x00\xe0\xff\x00\x00\xfe'\ 158 | b'\x07\x00\xe0\x07\x00\xf8\x07\xc0\xff\x07\xf8\x3f\x00\xf8\x03\x00'\ 159 | b'\xf8\x01\x00\xf8\x3f\x00\xc0\xff\x07\x00\xf8\x07\x00\xc0\x07\x00'\ 160 | b'\xfe\x07\xe0\xff\x01\xf8\x1f\x00\xf8\x01\x00\x38\x00\x00\x0e\x00'\ 161 | b'\x08\x00\x04\x18\x00\x07\x78\x80\x07\xf8\xe0\x07\xf0\xf3\x01\xc0'\ 162 | b'\xff\x00\x00\x3f\x00\x80\x7f\x00\xe0\xff\x00\xf0\xf1\x03\xf8\xc0'\ 163 | b'\x07\x38\x80\x07\x18\x00\x06\x08\x00\x04\x0d\x00\x18\x00\x00\x38'\ 164 | b'\x00\x00\xf8\x00\x00\xf0\x03\x00\xc0\x0f\x00\x00\xff\x07\x00\xfc'\ 165 | b'\x07\x00\xff\x07\xc0\x0f\x00\xf0\x03\x00\xf8\x00\x00\x38\x00\x00'\ 166 | b'\x18\x00\x00\x0d\x00\x00\x00\x07\x38\x80\x07\x38\xc0\x07\x38\xe0'\ 167 | b'\x07\x38\x78\x07\x38\x3c\x07\x38\x1e\x07\x38\x0f\x07\xf8\x07\x07'\ 168 | b'\xf8\x01\x07\xf8\x00\x07\x78\x00\x07\x38\x00\x07\x07\x00\xff\xff'\ 169 | b'\x3f\xff\xff\x3f\xff\xff\x3f\x07\x00\x38\x07\x00\x38\x07\x00\x38'\ 170 | b'\x00\x00\x00\x08\x00\x04\x00\x00\x3c\x00\x00\xfc\x01\x00\xfc\x1f'\ 171 | b'\x00\xe0\xff\x00\x00\xfe\x07\x00\xf0\x07\x00\x00\x07\x07\x00\x07'\ 172 | b'\x00\x38\x07\x00\x38\x07\x00\x38\xff\xff\x3f\xff\xff\x3f\xff\xff'\ 173 | b'\x3f\x00\x00\x00\x0c\x00\x00\x18\x00\x00\x1f\x00\xc0\x1f\x00\xf8'\ 174 | b'\x03\x00\xf8\x00\x00\x78\x00\x00\xf8\x03\x00\xe0\x0f\x00\x00\x1f'\ 175 | b'\x00\x00\x1c\x00\x00\x10\x00\x00\x00\x00\x12\x00\x00\x00\x38\x00'\ 176 | b'\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ 177 | b'\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38'\ 178 | b'\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00'\ 179 | b'\x00\x38\x09\x00\x04\x00\x00\x04\x00\x00\x0c\x00\x00\x1c\x00\x00'\ 180 | b'\x18\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d'\ 181 | b'\x00\x00\xc0\x01\x00\xe4\x03\x00\xe6\x07\x00\x37\x07\x80\x37\x07'\ 182 | b'\x80\x33\x07\x80\x33\x07\x80\x33\x07\x80\x93\x03\x80\xff\x07\x00'\ 183 | b'\xff\x07\x00\xfe\x07\x00\x00\x00\x0e\x00\xfc\xff\x07\xfc\xff\x07'\ 184 | b'\xfc\xff\x07\x00\x87\x03\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80'\ 185 | b'\x03\x07\x80\x87\x07\x00\xff\x03\x00\xfe\x01\x00\xf8\x00\x00\x00'\ 186 | b'\x00\x00\x00\x00\x0d\x00\x00\x78\x00\x00\xfe\x01\x00\xff\x03\x00'\ 187 | b'\x87\x03\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x87'\ 188 | b'\x07\x00\x87\x03\x00\x86\x01\x00\x88\x00\x00\x00\x00\x0e\x00\x00'\ 189 | b'\x78\x00\x00\xfe\x01\x00\xff\x03\x00\x87\x03\x80\x87\x07\x80\x03'\ 190 | b'\x07\x80\x03\x07\x80\x03\x07\x80\x87\x07\x00\x87\x03\xfc\xff\x07'\ 191 | b'\xfc\xff\x07\xfc\xff\x07\x00\x00\x00\x0d\x00\x00\x30\x00\x00\xfc'\ 192 | b'\x00\x00\xfe\x01\x00\xff\x03\x80\x77\x07\x80\x73\x07\x80\x73\x07'\ 193 | b'\x80\x73\x07\x80\xf7\x07\x00\xff\x03\x00\x7e\x03\x00\x7c\x00\x00'\ 194 | b'\x60\x00\x07\x00\x80\x03\x00\xf0\xff\x07\xfc\xff\x07\xfc\xff\x07'\ 195 | b'\x9e\x03\x00\x8e\x03\x00\x00\x00\x00\x0e\x00\x00\x78\x00\x00\xfe'\ 196 | b'\x01\x00\xff\x23\x00\x87\x73\x80\x87\xf7\x80\x03\xe7\x80\x03\xe7'\ 197 | b'\x80\x03\xe7\x00\x87\xe7\x00\x87\xf3\x80\xff\x7f\x80\xff\x3f\x80'\ 198 | b'\xff\x1f\x00\x00\x00\x0d\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff\x07'\ 199 | b'\x00\x07\x00\x80\x03\x00\x80\x03\x00\x80\x03\x00\x80\x07\x00\x00'\ 200 | b'\xff\x07\x00\xff\x07\x00\xfc\x07\x00\x00\x00\x00\x00\x00\x05\x00'\ 201 | b'\xb8\xff\x07\xb8\xff\x07\xb8\xff\x07\x00\x00\x00\x00\x00\x00\x05'\ 202 | b'\x00\x00\x00\xe0\x00\x00\xe0\xb8\xff\xff\xb8\xff\xff\xb8\xff\x7f'\ 203 | b'\x0c\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff\x07\x00\x78\x00\x00\x3c'\ 204 | b'\x00\x00\xfe\x00\x80\xff\x01\x80\xe7\x07\x80\x83\x07\x80\x01\x07'\ 205 | b'\x80\x00\x04\x00\x00\x00\x05\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff'\ 206 | b'\x07\x00\x00\x00\x00\x00\x00\x14\x00\x80\xff\x07\x80\xff\x07\x80'\ 207 | b'\xff\x07\x00\x07\x00\x80\x03\x00\x80\x03\x00\x80\x03\x00\x80\x07'\ 208 | b'\x00\x00\xff\x07\x00\xfe\x07\x00\xff\x07\x80\x07\x00\x80\x03\x00'\ 209 | b'\x80\x03\x00\x80\x07\x00\x00\xff\x07\x00\xff\x07\x00\xfc\x07\x00'\ 210 | b'\x00\x00\x00\x00\x00\x0d\x00\x80\xff\x07\x80\xff\x07\x80\xff\x07'\ 211 | b'\x00\x07\x00\x80\x03\x00\x80\x03\x00\x80\x03\x00\x80\x07\x00\x00'\ 212 | b'\xff\x07\x00\xff\x07\x00\xfc\x07\x00\x00\x00\x00\x00\x00\x0d\x00'\ 213 | b'\x00\x78\x00\x00\xfe\x01\x00\xff\x03\x00\x87\x03\x80\x03\x07\x80'\ 214 | b'\x03\x07\x80\x03\x07\x80\x03\x07\x00\x87\x03\x00\xff\x03\x00\xfe'\ 215 | b'\x01\x00\x78\x00\x00\x00\x00\x0e\x00\x80\xff\xff\x80\xff\xff\x80'\ 216 | b'\xff\xff\x00\x87\x03\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x03'\ 217 | b'\x07\x80\x87\x07\x00\xff\x03\x00\xfe\x01\x00\x7c\x00\x00\x00\x00'\ 218 | b'\x00\x00\x00\x0e\x00\x00\x78\x00\x00\xfe\x01\x00\xff\x03\x00\x87'\ 219 | b'\x03\x80\x87\x07\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x87\x07'\ 220 | b'\x00\x87\x03\x80\xff\xff\x80\xff\xff\x80\xff\xff\x00\x00\x00\x08'\ 221 | b'\x00\x80\xff\x07\x80\xff\x07\x80\xff\x07\x00\x07\x00\x80\x03\x00'\ 222 | b'\x80\x03\x00\x80\x03\x00\x00\x00\x00\x0c\x00\x00\x80\x00\x00\x8e'\ 223 | b'\x01\x00\x9f\x03\x80\x9f\x07\x80\x3b\x07\x80\x33\x07\x80\x33\x07'\ 224 | b'\x80\x33\x07\x00\xf7\x07\x00\xe7\x03\x00\xc6\x01\x00\x00\x00\x07'\ 225 | b'\x00\x80\x03\x00\xfc\xff\x00\xfc\xff\x03\xfc\xff\x07\x80\x03\x07'\ 226 | b'\x80\x03\x07\x00\x00\x00\x0d\x00\x80\xff\x00\x80\xff\x03\x80\xff'\ 227 | b'\x03\x00\x80\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x80\x03'\ 228 | b'\x80\xff\x07\x80\xff\x07\x80\xff\x07\x00\x00\x00\x00\x00\x00\x0c'\ 229 | b'\x00\x80\x01\x00\x80\x0f\x00\x80\x3f\x00\x00\xfe\x01\x00\xf0\x07'\ 230 | b'\x00\x80\x07\x00\xc0\x07\x00\xf8\x07\x00\xff\x00\x80\x3f\x00\x80'\ 231 | b'\x07\x00\x80\x00\x00\x12\x00\x80\x01\x00\x80\x0f\x00\x80\xff\x00'\ 232 | b'\x00\xfe\x07\x00\xe0\x07\x00\xc0\x07\x00\xfc\x07\x80\xff\x00\x80'\ 233 | b'\x0f\x00\x80\x3f\x00\x00\xff\x01\x00\xf0\x07\x00\x80\x07\x00\xf8'\ 234 | b'\x07\x80\xff\x03\x80\x3f\x00\x80\x07\x00\x80\x00\x00\x0c\x00\x80'\ 235 | b'\x00\x04\x80\x01\x07\x80\x87\x07\x80\xef\x07\x00\xfe\x01\x00\x7c'\ 236 | b'\x00\x00\xfe\x00\x00\xff\x03\x80\xc7\x07\x80\x03\x07\x80\x00\x06'\ 237 | b'\x00\x00\x04\x0c\x00\x80\x01\x00\x80\x07\xe0\x80\x3f\xe0\x00\xff'\ 238 | b'\xf0\x00\xf8\xff\x00\xc0\x7f\x00\xc0\x1f\x00\xf8\x07\x00\xff\x00'\ 239 | b'\x80\x1f\x00\x80\x07\x00\x80\x00\x00\x0b\x00\x80\x03\x07\x80\x83'\ 240 | b'\x07\x80\xc3\x07\x80\xe3\x07\x80\xf3\x07\x80\x7b\x07\x80\x3f\x07'\ 241 | b'\x80\x1f\x07\x80\x0f\x07\x80\x07\x07\x00\x00\x07\x08\x00\x00\x0c'\ 242 | b'\x00\x00\x1e\x00\xfc\xff\x0f\xfe\xff\x1f\xfe\xf3\x1f\x06\x00\x38'\ 243 | b'\x06\x00\x38\x00\x00\x00\x05\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff'\ 244 | b'\x07\x00\x00\x00\x00\x00\x00\x07\x00\x06\x00\x38\xfe\xf3\x1f\xfe'\ 245 | b'\xff\x1f\xfc\xff\x0f\x00\x1e\x00\x00\x0c\x00\x00\x00\x00\x09\x00'\ 246 | b'\x18\x00\x00\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\x18\x00\x00\x18'\ 247 | b'\x00\x00\x1c\x00\x00\x04\x00\x00\x00\x00\x00' 248 | 249 | _index =\ 250 | b'\x00\x00\x26\x00\x3d\x00\x51\x00\x6b\x00\x91\x00\xb4\x00\xec\x00'\ 251 | b'\x1b\x01\x29\x01\x40\x01\x57\x01\x74\x01\x9a\x01\xab\x01\xc2\x01'\ 252 | b'\xd3\x01\xed\x01\x16\x02\x30\x02\x59\x02\x7f\x02\xa5\x02\xcb\x02'\ 253 | b'\xf4\x02\x17\x03\x3d\x03\x66\x03\x77\x03\x88\x03\xa8\x03\xcb\x03'\ 254 | b'\xeb\x03\x11\x04\x46\x04\x75\x04\xa4\x04\xd6\x04\x05\x05\x31\x05'\ 255 | b'\x5a\x05\x8f\x05\xbe\x05\xd2\x05\xf8\x05\x24\x06\x4a\x06\x82\x06'\ 256 | b'\xb1\x06\xe6\x06\x12\x07\x47\x07\x76\x07\xa2\x07\xc8\x07\xf4\x07'\ 257 | b'\x20\x08\x5e\x08\x8a\x08\xb3\x08\xdc\x08\xf3\x08\x0d\x09\x24\x09'\ 258 | b'\x4a\x09\x82\x09\x9f\x09\xc8\x09\xf4\x09\x1d\x0a\x49\x0a\x72\x0a'\ 259 | b'\x89\x0a\xb5\x0a\xde\x0a\xef\x0a\x00\x0b\x26\x0b\x37\x0b\x75\x0b'\ 260 | b'\x9e\x0b\xc7\x0b\xf3\x0b\x1f\x0c\x39\x0c\x5f\x0c\x76\x0c\x9f\x0c'\ 261 | b'\xc5\x0c\xfd\x0c\x23\x0d\x49\x0d\x6c\x0d\x86\x0d\x97\x0d\xae\x0d'\ 262 | b'\xcb\x0d' 263 | 264 | _mvfont = memoryview(_font) 265 | 266 | def _chr_addr(ordch): 267 | offset = 2 * (ordch - 32) 268 | return int.from_bytes(_index[offset:offset + 2], 'little') 269 | 270 | def get_width(s): 271 | width = 0 272 | for ch in s: 273 | ordch = ord(ch) 274 | ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 275 | offset = _chr_addr(ordch) 276 | width += int.from_bytes(_font[offset:offset + 2], 'little') 277 | return width 278 | 279 | def get_ch(ch): 280 | ordch = ord(ch) 281 | ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 282 | offset = _chr_addr(ordch) 283 | width = int.from_bytes(_font[offset:offset + 2], 'little') 284 | next_offs = _chr_addr(ordch +1) 285 | return _mvfont[offset + 2:next_offs], width 286 | 287 | -------------------------------------------------------------------------------- /breakout.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------- 2 | # Breakout.py Game 3 | # Use common game module "gameESP.py" for ESP8266 or ESP32 4 | # by Billy Cheung 2019 10 26 5 | # 6 | import gc 7 | import sys 8 | gc.collect() 9 | print (gc.mem_free()) 10 | import network 11 | import utime 12 | from utime import sleep_ms,ticks_ms, ticks_us, ticks_diff 13 | from machine import Pin, SPI, PWM, ADC 14 | from math import sqrt 15 | import gameESP 16 | # all dislplay, buttons, paddle, sound logics are in gameESP.mpy module 17 | from gameESP import * 18 | g=gameESP() 19 | paddle_width = 22 20 | frameRate = 30 21 | 22 | class Ball(object): 23 | """Ball.""" 24 | 25 | def __init__(self, x, y, x_speed, y_speed, display, width=2, height=2, 26 | frozen=False): 27 | self.x = x 28 | self.y = y 29 | self.x2 = x + width - 1 30 | self.y2 = y + height - 1 31 | self.prev_x = x 32 | self.prev_y = y 33 | self.width = width 34 | self.height = height 35 | self.center = width // 2 36 | self.max_x_speed = 3 37 | self.max_y_speed = 3 38 | self.frozen = frozen 39 | self.display = display 40 | self.x_speed = x_speed 41 | self.y_speed = y_speed 42 | self.x_speed2 = 0.0 43 | self.y_speed2 = 0.0 44 | self.created = ticks_ms() 45 | 46 | def clear(self): 47 | """Clear ball.""" 48 | self.display.fill_rect(self.x, self.y, self.width, self.height, 0) 49 | 50 | def clear_previous(self): 51 | """Clear prevous ball position.""" 52 | self.display.fill_rect(self.prev_x, self.prev_y, 53 | self.width, self.height, 0) 54 | 55 | def draw(self): 56 | """Draw ball.""" 57 | self.clear_previous() 58 | self.display.fill_rect( self.x, self.y, 59 | self.width, self.height,1) 60 | 61 | def set_position(self, paddle_x, paddle_y, paddle_x2, paddle_center): 62 | bounced = False 63 | """Set ball position.""" 64 | self.prev_x = self.x 65 | self.prev_y = self.y 66 | # Check if frozen to paddle 67 | if self.frozen: 68 | # Freeze ball to top center of paddle 69 | self.x = paddle_x + (paddle_center - self.center) 70 | self.y = paddle_y - self.height 71 | if ticks_diff(ticks_ms(), self.created) >= 2000: 72 | # Release frozen ball after 2 seconds 73 | self.frozen = False 74 | else: 75 | return 76 | self.x += int(self.x_speed) + int(self.x_speed2) 77 | self.x_speed2 -= int(self.x_speed2) 78 | self.x_speed2 += self.x_speed - int(self.x_speed) 79 | 80 | self.y += int(self.y_speed) + int(self.y_speed2) 81 | self.y_speed2 -= int(self.y_speed2) 82 | self.y_speed2 += self.y_speed - int(self.y_speed) 83 | 84 | # Bounces off walls 85 | if self.y < 10: 86 | self.y = 10 87 | self.y_speed = -self.y_speed 88 | bounced = True 89 | if self.x + self.width >= 125: 90 | self.x = 125 - self.width 91 | self.x_speed = -self.x_speed 92 | bounced = True 93 | elif self.x < 3: 94 | self.x = 3 95 | self.x_speed = -self.x_speed 96 | bounced = True 97 | 98 | # Check for collision with Paddle 99 | if (self.y2 >= paddle_y and 100 | self.x <= paddle_x2 and 101 | self.x2 >= paddle_x): 102 | # Ball bounces off paddle 103 | self.y = paddle_y - (self.height + 1) 104 | ratio = ((self.x + self.center) - 105 | (paddle_x + paddle_center)) / paddle_center 106 | self.x_speed = ratio * self.max_x_speed 107 | self.y_speed = -sqrt(max(1, self.max_y_speed ** 2 - self.x_speed ** 2)) 108 | bounced = True 109 | 110 | self.x2 = self.x + self.width - 1 111 | self.y2 = self.y + self.height - 1 112 | return bounced 113 | 114 | 115 | class Brick(object): 116 | """Brick.""" 117 | 118 | def __init__(self, x, y, color, display, width=12, height=3): 119 | """Initialize brick. 120 | 121 | Args: 122 | x, y (int): X,Y coordinates. 123 | color (string): Blue, Green, Pink, Red or Yellow. 124 | display (SSD1351): OLED g.display. 125 | width (Optional int): Blick width 126 | height (Optional int): Blick height 127 | """ 128 | self.x = x 129 | self.y = y 130 | self.x2 = x + width - 1 131 | self.y2 = y + height - 1 132 | self.center_x = x + (width // 2) 133 | self.center_y = y + (height // 2) 134 | self.color = color 135 | self.width = width 136 | self.height = height 137 | self.display = display 138 | self.draw() 139 | 140 | def bounce(self, ball_x, ball_y, ball_x2, ball_y2, 141 | x_speed, y_speed, 142 | ball_center_x, ball_center_y): 143 | """Determine bounce for ball collision with brick.""" 144 | x = self.x 145 | y = self.y 146 | x2 = self.x2 147 | y2 = self.y2 148 | center_x = self.center_x 149 | center_y = self.center_y 150 | if ((ball_center_x > center_x) and 151 | (ball_center_y > center_y)): 152 | if (ball_center_x - x2) < (ball_center_y - y2): 153 | y_speed = -y_speed 154 | elif (ball_center_x - x2) > (ball_center_y - y2): 155 | x_speed = -x_speed 156 | else: 157 | x_speed = -x_speed 158 | y_speed = -y_speed 159 | elif ((ball_center_x > center_x) and 160 | (ball_center_y < center_y)): 161 | if (ball_center_x - x2) < -(ball_center_y - y): 162 | y_speed = -y_speed 163 | elif (ball_center_x - x2) > -(ball_center_y - y): 164 | x_speed = -x_speed 165 | else: 166 | x_speed = -x_speed 167 | y_speed = -y_speed 168 | elif ((ball_center_x < center_x) and 169 | (ball_center_y < center_y)): 170 | if -(ball_center_x - x) < -(ball_center_y - y): 171 | y_speed = -y_speed 172 | elif -(ball_center_x - x) > -(ball_center_y - y): 173 | y_speed = -y_speed 174 | else: 175 | x_speed = -x_speed 176 | y_speed = -y_speed 177 | elif ((ball_center_x < center_x) and 178 | (ball_center_y > center_y)): 179 | if -(ball_center_x - x) < (ball_center_y - y2): 180 | y_speed = -y_speed 181 | elif -(ball_center_x - x) > (ball_center_y - y2): 182 | x_speed = -x_speed 183 | else: 184 | x_speed = -x_speed 185 | y_speed = -y_speed 186 | 187 | return [x_speed, y_speed] 188 | 189 | def clear(self): 190 | """Clear brick.""" 191 | self.display.fill_rect(self.x, self.y, self.width, self.height, 0) 192 | 193 | def draw(self): 194 | """Draw brick.""" 195 | self.display.rect(self.x, self.y, self.width, self.height, 1) 196 | 197 | 198 | class Life(object): 199 | """Life.""" 200 | 201 | def __init__(self, index, display, width=4, height=6): 202 | """Initialize life. 203 | 204 | Args: 205 | index (int): Life number (1-based). 206 | display (SSD1351): OLED g.display. 207 | width (Optional int): Life width 208 | height (Optional int): Life height 209 | """ 210 | margin = 5 211 | self.display = display 212 | self.x = g.display.width - (index * (width + margin)) 213 | self.y = 0 214 | self.width = width 215 | self.height = height 216 | self.draw() 217 | 218 | def clear(self): 219 | """Clear life.""" 220 | self.display.fill_rect(self.x, self.y, self.width, self.height, 0) 221 | 222 | def draw(self): 223 | """Draw life.""" 224 | self.display.fill_rect(self.x, self.y, 225 | self.width, self.height,1) 226 | 227 | 228 | class Paddle(object): 229 | """Paddle.""" 230 | 231 | def __init__(self, display, width, height): 232 | """Initialize paddle. 233 | 234 | Args: 235 | display (SSD1306): OLED g.display. 236 | width (Optional int): Paddle width 237 | height (Optional int): Paddle height 238 | """ 239 | self.x = 55 240 | self.y = 60 241 | self.x2 = self.x + width - 1 242 | self.y2 = self.y + height - 1 243 | self.width = width 244 | self.height = height 245 | self.center = width // 2 246 | self.display = display 247 | 248 | def clear(self): 249 | """Clear paddle.""" 250 | self.display.fill_rect(self.x, self.y, self.width, self.height, 0) 251 | 252 | 253 | def draw(self): 254 | """Draw paddle.""" 255 | self.display.fill_rect(self.x, self.y,self.width, self.height,1) 256 | 257 | def h_position(self, x): 258 | """Set paddle position. 259 | 260 | Args: 261 | x (int): X coordinate. 262 | """ 263 | new_x = max(3,min (x, 125-self.width)) 264 | if new_x != self.x : # Check if paddle moved 265 | prev_x = self.x # Store previous x position 266 | self.x = new_x 267 | self.x2 = self.x + self.width - 1 268 | self.y2 = self.y + self.height - 1 269 | self.draw() 270 | # Clear previous paddle 271 | if x > prev_x: 272 | self.display.fill_rect(prev_x, self.y, 273 | x - prev_x, self.height, 0) 274 | else: 275 | self.display.fill_rect(x + self.width, self.y, 276 | (prev_x + self.width) - 277 | (x + self.width), 278 | self.height, 0) 279 | else: 280 | self.draw() 281 | 282 | class Score(object): 283 | """Score.""" 284 | 285 | def __init__(self, display): 286 | """Initialize score. 287 | 288 | Args: 289 | display (SSD1306): OLED g.display. 290 | """ 291 | margin = 5 292 | self.display = display 293 | self.display.text('S:', margin, 0, 1) 294 | self.x = 20 + margin 295 | self.y = 0 296 | self.value = 0 297 | self.draw() 298 | 299 | def draw(self): 300 | """Draw score value.""" 301 | self.display.fill_rect(self.x, self.y, 20, 8, 0) 302 | self.display.text( str(self.value), self.x, self.y,1) 303 | 304 | def game_over(self): 305 | """Display game_over.""" 306 | self.display.text('GAME OVER', (self.display.width // 2) - 30, 307 | int(self.display.height / 1.5), 1) 308 | 309 | def increment(self, points): 310 | """Increase score by specified points.""" 311 | self.value += points 312 | self.draw() 313 | 314 | def load_level(level, display) : 315 | global frameRate 316 | if demo : 317 | frameRate = 60 + level * 10 318 | else : 319 | frameRate = 25 + level * 5 320 | bricks = [] 321 | for row in range(12, 20 + 6 * level , 6): 322 | brick_color = 1 323 | for col in range(8, 112, 15 ): 324 | bricks.append(Brick(col, row, brick_color, display)) 325 | 326 | return bricks 327 | 328 | demoOn = False 329 | exitGame = False 330 | while not exitGame : 331 | paddle_width = 22 332 | frameRate = 30 333 | gc.collect() 334 | print (gc.mem_free()) 335 | 336 | 337 | gameOver = False 338 | usePaddle = False 339 | if demoOn : 340 | demo = True 341 | else : 342 | demo = False 343 | 344 | 345 | while True: 346 | g.display.fill(0) 347 | g.display.text('BREAKOUT', 0, 0, 1) 348 | g.display.rect(90,0, g.max_vol*4+2,6,1) 349 | g.display.fill_rect(91,1, g.vol * 4,4,1) 350 | g.display.text('A Start L Quit', 0, 10, 1) 351 | if usePaddle : 352 | g.display.text('U Paddle', 0,20, 1) 353 | else : 354 | g.display.text('U Button', 0,20, 1) 355 | if demo : 356 | g.display.text('D AI-Player', 0,30, 1) 357 | else : 358 | g.display.text('D 1-Player', 0,30, 1) 359 | g.display.text('R Frame/s {}'.format(g.frameRate), 0,40, 1) 360 | g.display.text('B + U/D Sound', 0, 50, 1) 361 | g.display.show() 362 | 363 | g.getBtn() 364 | if g.setVol() : 365 | pass 366 | elif g.justReleased(g.btnL) : 367 | exitGame = True 368 | gameOver= True 369 | break 370 | elif g.justPressed(g.btnA) or demoOn : 371 | if demo : 372 | g.display.fill(0) 373 | g.display.text('DEMO', 5, 0, 1) 374 | g.display.text('B to Stop', 5, 30, 1) 375 | g.display.show() 376 | sleep_ms(1000) 377 | demoOn = True 378 | break 379 | elif g.justPressed(g.btnU) : 380 | usePaddle = not usePaddle 381 | elif g.justPressed(g.btnD) : 382 | demo = not demo 383 | elif g.justPressed(g.btnR) : 384 | if g.pressed(g.btnB) : 385 | g.frameRate = g.frameRate - 5 if g.frameRate > 5 else 100 386 | else : 387 | g.frameRate = g.frameRate + 5 if g.frameRate < 100 else 5 388 | 389 | if not exitGame : 390 | g.display.fill(0) 391 | 392 | # Generate bricks 393 | MAX_LEVEL = const(5) 394 | level = 1 395 | bricks = load_level(level, g.display) 396 | 397 | # Initialize paddle 398 | paddle = Paddle(g.display, paddle_width, 3) 399 | 400 | # Initialize score 401 | score = Score(g.display) 402 | 403 | # Initialize balls 404 | balls = [] 405 | # Add first ball 406 | balls.append(Ball(59, 58, -2, -1, g.display, frozen=True)) 407 | 408 | # Initialize lives 409 | lives = [] 410 | for i in range(1, 3): 411 | lives.append(Life(i, g.display)) 412 | 413 | prev_paddle_vect = 0 414 | 415 | 416 | g.display.show() 417 | 418 | 419 | try: 420 | while not gameOver : 421 | g.getBtn() 422 | if demo : 423 | if g.justReleased (g.btnB) : 424 | g.display.text('Demo stopped', 5, 30, 1) 425 | g.display.show() 426 | sleep_ms(1000) 427 | gameOver = True 428 | demoOn = False 429 | else : 430 | paddle.h_position(balls[0].x - 5 + g.random (0,7)) 431 | elif usePaddle : 432 | paddle.h_position(int(g.getPaddle() // 9.57)) 433 | else : 434 | paddle_vect = 0 435 | if g.pressed(g.btnL | g.btnA) : 436 | paddle_vect = -1 437 | elif g.pressed(g.btnR | g.btnB) : 438 | paddle_vect = 1 439 | if paddle_vect != prev_paddle_vect : 440 | paddle_vect *= 3 441 | else : 442 | paddle_vect *= 5 443 | paddle.h_position(paddle.x + paddle_vect) 444 | prev_paddle_vect = paddle_vect 445 | 446 | # Handle balls 447 | score_points = 0 448 | for ball in balls: 449 | # move ball and check if bounced off walls and paddle 450 | if ball.set_position(paddle.x, paddle.y,paddle.x2, paddle.center): 451 | g.playSound(900, 10) 452 | # Check for collision with bricks if not frozen 453 | if not ball.frozen: 454 | prior_collision = False 455 | ball_x = ball.x 456 | ball_y = ball.y 457 | ball_x2 = ball.x2 458 | ball_y2 = ball.y2 459 | ball_center_x = ball.x + ((ball.x2 + 1 - ball.x) // 2) 460 | ball_center_y = ball.y + ((ball.y2 + 1 - ball.y) // 2) 461 | 462 | # Check for hits 463 | for brick in bricks: 464 | if(ball_x2 >= brick.x and 465 | ball_x <= brick.x2 and 466 | ball_y2 >= brick.y and 467 | ball_y <= brick.y2): 468 | # Hit 469 | if not prior_collision: 470 | ball.x_speed, ball.y_speed = brick.bounce( 471 | ball.x, 472 | ball.y, 473 | ball.x2, 474 | ball.y2, 475 | ball.x_speed, 476 | ball.y_speed, 477 | ball_center_x, 478 | ball_center_y) 479 | g.playTone('c6', 10) 480 | prior_collision = True 481 | score_points += 1 482 | brick.clear() 483 | bricks.remove(brick) 484 | 485 | # Check for missed 486 | if ball.y2 > g.display.height - 2: 487 | ball.clear_previous() 488 | balls.remove(ball) 489 | if not balls: 490 | # Lose life if last ball on screen 491 | if len(lives) == 0: 492 | score.game_over() 493 | g.playTone('g4', 500) 494 | g.playTone('c5', 200) 495 | g.playTone('f4', 500) 496 | gameOver = True 497 | else: 498 | # Subtract Life 499 | lives.pop().clear() 500 | # Add ball 501 | balls.append(Ball(59, 58, 2, -3, g.display, 502 | frozen=True)) 503 | else: 504 | # Draw ball 505 | ball.draw() 506 | # Update score if changed 507 | if score_points: 508 | score.increment(score_points) 509 | 510 | # Check for level completion 511 | if not bricks: 512 | for ball in balls: 513 | ball.clear() 514 | balls.clear() 515 | level += 1 516 | paddle_width -=2 517 | if level > MAX_LEVEL: 518 | level = 1 519 | bricks = load_level(level, g.display) 520 | balls.append(Ball(59, 58, -2, -1, g.display, frozen=True)) 521 | g.playTone('c5', 20) 522 | g.playTone('d5', 20) 523 | g.playTone('e5', 20) 524 | g.playTone('f5', 20) 525 | g.playTone('g5', 20) 526 | g.playTone('a5', 20) 527 | g.playTone('b5', 20) 528 | g.playTone('c6', 20) 529 | g.display_and_wait() 530 | except KeyboardInterrupt: 531 | g.display.cleanup() 532 | sleep_ms(2000) 533 | if g.ESP32 : 534 | g.deinit() 535 | del sys.modules["gameESP"] 536 | gc.collect() 537 | -------------------------------------------------------------------------------- /utils/lcd/font_to_py.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # Needs freetype-py>=1.0 4 | 5 | # Implements multi-pass solution to setting an exact font height 6 | 7 | # Some code adapted from Daniel Bader's work at the following URL 8 | # http://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python 9 | 10 | # The MIT License (MIT) 11 | # 12 | # Copyright (c) 2016 Peter Hinch 13 | # 14 | # Permission is hereby granted, free of charge, to any person obtaining a copy 15 | # of this software and associated documentation files (the "Software"), to deal 16 | # in the Software without restriction, including without limitation the rights 17 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | # copies of the Software, and to permit persons to whom the Software is 19 | # furnished to do so, subject to the following conditions: 20 | # 21 | # The above copyright notice and this permission notice shall be included in 22 | # all copies or substantial portions of the Software. 23 | # 24 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | # THE SOFTWARE. 31 | 32 | import argparse 33 | import sys 34 | import os 35 | import freetype 36 | 37 | # UTILITIES FOR WRITING PYTHON SOURCECODE TO A FILE 38 | 39 | # ByteWriter takes as input a variable name and data values and writes 40 | # Python source to an output stream of the form 41 | # my_variable = b'\x01\x02\x03\x04\x05\x06\x07\x08'\ 42 | 43 | # Lines are broken with \ for readability. 44 | 45 | 46 | class ByteWriter(object): 47 | bytes_per_line = 16 48 | 49 | def __init__(self, stream, varname): 50 | self.stream = stream 51 | self.stream.write('{} =\\\n'.format(varname)) 52 | self.bytecount = 0 # For line breaks 53 | 54 | def _eol(self): 55 | self.stream.write("'\\\n") 56 | 57 | def _eot(self): 58 | self.stream.write("'\n") 59 | 60 | def _bol(self): 61 | self.stream.write("b'") 62 | 63 | # Output a single byte 64 | def obyte(self, data): 65 | if not self.bytecount: 66 | self._bol() 67 | self.stream.write('\\x{:02x}'.format(data)) 68 | self.bytecount += 1 69 | self.bytecount %= self.bytes_per_line 70 | if not self.bytecount: 71 | self._eol() 72 | 73 | # Output from a sequence 74 | def odata(self, bytelist): 75 | for byt in bytelist: 76 | self.obyte(byt) 77 | 78 | # ensure a correct final line 79 | def eot(self): # User force EOL if one hasn't occurred 80 | if self.bytecount: 81 | self._eot() 82 | self.stream.write('\n') 83 | 84 | 85 | # Define a global 86 | def var_write(stream, name, value): 87 | stream.write('{} = {}\n'.format(name, value)) 88 | 89 | # FONT HANDLING 90 | 91 | 92 | class Bitmap(object): 93 | """ 94 | A 2D bitmap image represented as a list of byte values. Each byte indicates 95 | the state of a single pixel in the bitmap. A value of 0 indicates that the 96 | pixel is `off` and any other value indicates that it is `on`. 97 | """ 98 | def __init__(self, width, height, pixels=None): 99 | self.width = width 100 | self.height = height 101 | self.pixels = pixels or bytearray(width * height) 102 | 103 | def display(self): 104 | """Print the bitmap's pixels.""" 105 | for row in range(self.height): 106 | for col in range(self.width): 107 | char = '#' if self.pixels[row * self.width + col] else '.' 108 | print(char, end='') 109 | print() 110 | print() 111 | 112 | def bitblt(self, src, row): 113 | """Copy all pixels from `src` into this bitmap""" 114 | srcpixel = 0 115 | dstpixel = row * self.width 116 | row_offset = self.width - src.width 117 | 118 | for _ in range(src.height): 119 | for _ in range(src.width): 120 | self.pixels[dstpixel] = src.pixels[srcpixel] 121 | srcpixel += 1 122 | dstpixel += 1 123 | dstpixel += row_offset 124 | 125 | # Horizontal mapping generator function 126 | def get_hbyte(self, reverse): 127 | for row in range(self.height): 128 | col = 0 129 | while True: 130 | bit = col % 8 131 | if bit == 0: 132 | if col >= self.width: 133 | break 134 | byte = 0 135 | if col < self.width: 136 | if reverse: 137 | byte |= self.pixels[row * self.width + col] << bit 138 | else: 139 | # Normal map MSB of byte 0 is (0, 0) 140 | byte |= self.pixels[row * self.width + col] << (7 - bit) 141 | if bit == 7: 142 | yield byte 143 | col += 1 144 | 145 | # Vertical mapping 146 | def get_vbyte(self, reverse): 147 | for col in range(self.width): 148 | row = 0 149 | while True: 150 | bit = row % 8 151 | if bit == 0: 152 | if row >= self.height: 153 | break 154 | byte = 0 155 | if row < self.height: 156 | if reverse: 157 | byte |= self.pixels[row * self.width + col] << (7 - bit) 158 | else: 159 | # Normal map MSB of byte 0 is (0, 7) 160 | byte |= self.pixels[row * self.width + col] << bit 161 | if bit == 7: 162 | yield byte 163 | row += 1 164 | 165 | 166 | class Glyph(object): 167 | def __init__(self, pixels, width, height, top, advance_width): 168 | self.bitmap = Bitmap(width, height, pixels) 169 | 170 | # The glyph bitmap's top-side bearing, i.e. the vertical distance from 171 | # the baseline to the bitmap's top-most scanline. 172 | self.top = top 173 | 174 | # Ascent and descent determine how many pixels the glyph extends 175 | # above or below the baseline. 176 | self.descent = max(0, self.height - self.top) 177 | self.ascent = max(0, max(self.top, self.height) - self.descent) 178 | 179 | # The advance width determines where to place the next character 180 | # horizontally, that is, how many pixels we move to the right to 181 | # draw the next glyph. 182 | self.advance_width = advance_width 183 | 184 | @property 185 | def width(self): 186 | return self.bitmap.width 187 | 188 | @property 189 | def height(self): 190 | return self.bitmap.height 191 | 192 | @staticmethod 193 | def from_glyphslot(slot): 194 | """Construct and return a Glyph object from a FreeType GlyphSlot.""" 195 | pixels = Glyph.unpack_mono_bitmap(slot.bitmap) 196 | width, height = slot.bitmap.width, slot.bitmap.rows 197 | top = slot.bitmap_top 198 | 199 | # The advance width is given in FreeType's 26.6 fixed point format, 200 | # which means that the pixel values are multiples of 64. 201 | advance_width = slot.advance.x / 64 202 | 203 | return Glyph(pixels, width, height, top, advance_width) 204 | 205 | @staticmethod 206 | def unpack_mono_bitmap(bitmap): 207 | """ 208 | Unpack a freetype FT_LOAD_TARGET_MONO glyph bitmap into a bytearray 209 | where each pixel is represented by a single byte. 210 | """ 211 | # Allocate a bytearray of sufficient size to hold the glyph bitmap. 212 | data = bytearray(bitmap.rows * bitmap.width) 213 | 214 | # Iterate over every byte in the glyph bitmap. Note that we're not 215 | # iterating over every pixel in the resulting unpacked bitmap -- 216 | # we're iterating over the packed bytes in the input bitmap. 217 | for row in range(bitmap.rows): 218 | for byte_index in range(bitmap.pitch): 219 | 220 | # Read the byte that contains the packed pixel data. 221 | byte_value = bitmap.buffer[row * bitmap.pitch + byte_index] 222 | 223 | # We've processed this many bits (=pixels) so far. This 224 | # determines where we'll read the next batch of pixels from. 225 | num_bits_done = byte_index * 8 226 | 227 | # Pre-compute where to write the pixels that we're going 228 | # to unpack from the current byte in the glyph bitmap. 229 | rowstart = row * bitmap.width + byte_index * 8 230 | 231 | # Iterate over every bit (=pixel) that's still a part of the 232 | # output bitmap. Sometimes we're only unpacking a fraction of 233 | # a byte because glyphs may not always fit on a byte boundary. 234 | # So we make sure to stop if we unpack past the current row 235 | # of pixels. 236 | for bit_index in range(min(8, bitmap.width - num_bits_done)): 237 | 238 | # Unpack the next pixel from the current glyph byte. 239 | bit = byte_value & (1 << (7 - bit_index)) 240 | 241 | # Write the pixel to the output bytearray. We ensure that 242 | # `off` pixels have a value of 0 and `on` pixels have a 243 | # value of 1. 244 | data[rowstart + bit_index] = 1 if bit else 0 245 | 246 | return data 247 | 248 | 249 | # A Font object is a dictionary of ASCII chars indexed by a character e.g. 250 | # myfont['a'] 251 | # Each entry comprises a list 252 | # [0] A Bitmap instance containing the character 253 | # [1] The width of the character data including advance (actual data stored) 254 | # Public attributes: 255 | # height (in pixels) of all characters 256 | # width (in pixels) for monospaced output (advance width of widest char) 257 | class Font(dict): 258 | def __init__(self, filename, size, minchar, maxchar, monospaced, defchar): 259 | super().__init__() 260 | self._face = freetype.Face(filename) 261 | if defchar is None: # Binary font 262 | self.charset = [chr(char) for char in range(minchar, maxchar + 1)] 263 | else: 264 | self.charset = [chr(defchar)] + [chr(char) for char in range(minchar, maxchar + 1)] 265 | self.max_width = self.get_dimensions(size) 266 | self.width = self.max_width if monospaced else 0 267 | for char in self.charset: # Populate dictionary 268 | self._render_char(char) 269 | 270 | # n-pass solution to setting a precise height. 271 | def get_dimensions(self, required_height): 272 | error = 0 273 | height = required_height 274 | for npass in range(10): 275 | height += error 276 | self._face.set_pixel_sizes(0, height) 277 | max_descent = 0 278 | 279 | # For each character in the charset string we get the glyph 280 | # and update the overall dimensions of the resulting bitmap. 281 | max_width = 0 282 | max_ascent = 0 283 | for char in self.charset: 284 | glyph = self._glyph_for_character(char) 285 | max_ascent = max(max_ascent, glyph.ascent) 286 | max_descent = max(max_descent, glyph.descent) 287 | # for a few chars e.g. _ glyph.width > glyph.advance_width 288 | max_width = int(max(max_width, glyph.advance_width, 289 | glyph.width)) 290 | 291 | new_error = required_height - (max_ascent + max_descent) 292 | if (new_error == 0) or (abs(new_error) - abs(error) == 0): 293 | break 294 | error = new_error 295 | self.height = int(max_ascent + max_descent) 296 | st = 'Height set in {} passes. Actual height {} pixels.\nMax character width {} pixels.' 297 | print(st.format(npass + 1, self.height, max_width)) 298 | self._max_descent = int(max_descent) 299 | return max_width 300 | 301 | 302 | def _glyph_for_character(self, char): 303 | # Let FreeType load the glyph for the given character and tell it to 304 | # render a monochromatic bitmap representation. 305 | self._face.load_char(char, freetype.FT_LOAD_RENDER | 306 | freetype.FT_LOAD_TARGET_MONO) 307 | return Glyph.from_glyphslot(self._face.glyph) 308 | 309 | def _render_char(self, char): 310 | glyph = self._glyph_for_character(char) 311 | char_width = int(max(glyph.width, glyph.advance_width)) # Actual width 312 | width = self.width if self.width else char_width # Space required if monospaced 313 | outbuffer = Bitmap(width, self.height) 314 | 315 | # The vertical drawing position should place the glyph 316 | # on the baseline as intended. 317 | row = self.height - int(glyph.ascent) - self._max_descent 318 | outbuffer.bitblt(glyph.bitmap, row) 319 | self[char] = [outbuffer, width, char_width] 320 | 321 | def stream_char(self, char, hmap, reverse): 322 | outbuffer, _, _ = self[char] 323 | if hmap: 324 | gen = outbuffer.get_hbyte(reverse) 325 | else: 326 | gen = outbuffer.get_vbyte(reverse) 327 | yield from gen 328 | 329 | def build_arrays(self, hmap, reverse): 330 | data = bytearray() 331 | index = bytearray((0, 0)) 332 | for char in self.charset: 333 | width = self[char][1] 334 | data += (width).to_bytes(2, byteorder='little') 335 | data += bytearray(self.stream_char(char, hmap, reverse)) 336 | index += (len(data)).to_bytes(2, byteorder='little') 337 | return data, index 338 | 339 | def build_binary_array(self, hmap, reverse, sig): 340 | data = bytearray((0x3f + sig, 0xe7, self.max_width, self.height)) 341 | for char in self.charset: 342 | width = self[char][2] 343 | data += bytes((width,)) 344 | data += bytearray(self.stream_char(char, hmap, reverse)) 345 | return data 346 | 347 | # PYTHON FILE WRITING 348 | 349 | STR01 = """# Code generated by font-to-py.py. 350 | # Font: {} 351 | version = '0.2' 352 | """ 353 | 354 | STR02 = """_mvfont = memoryview(_font) 355 | 356 | def _chr_addr(ordch): 357 | offset = 2 * (ordch - {}) 358 | return int.from_bytes(_index[offset:offset + 2], 'little') 359 | 360 | def get_width(s): 361 | width = 0 362 | for ch in s: 363 | ordch = ord(ch) 364 | ordch = ordch + 1 if ordch >= {} and ordch <= {} else 32 365 | offset = _chr_addr(ordch) 366 | width += int.from_bytes(_font[offset:offset + 2], 'little') 367 | return width 368 | 369 | def get_ch(ch): 370 | ordch = ord(ch) 371 | ordch = ordch + 1 if ordch >= {} and ordch <= {} else {} 372 | offset = _chr_addr(ordch) 373 | width = int.from_bytes(_font[offset:offset + 2], 'little') 374 | next_offs = _chr_addr(ordch +1) 375 | return _mvfont[offset + 2:next_offs], width 376 | 377 | """ 378 | 379 | def write_func(stream, name, arg): 380 | stream.write('def {}():\n return {}\n\n'.format(name, arg)) 381 | 382 | # filename, size, minchar=32, maxchar=126, monospaced=False, defchar=ord('?'): 383 | 384 | def write_font(op_path, font_path, height, monospaced, hmap, reverse, minchar, maxchar, defchar): 385 | try: 386 | fnt = Font(font_path, height, minchar, maxchar, monospaced, defchar) 387 | except freetype.ft_errors.FT_Exception: 388 | print("Can't open", font_path) 389 | return False 390 | try: 391 | with open(op_path, 'w') as stream: 392 | write_data(stream, fnt, font_path, monospaced, hmap, reverse, minchar, maxchar) 393 | except OSError: 394 | print("Can't open", op_path, 'for writing') 395 | return False 396 | return True 397 | 398 | 399 | def write_data(stream, fnt, font_path, monospaced, hmap, reverse, minchar, maxchar): 400 | height = fnt.height # Actual height, not target height 401 | stream.write(STR01.format(os.path.split(font_path)[1])) 402 | stream.write('\n') 403 | write_func(stream, 'height', height) 404 | write_func(stream, 'max_width', fnt.max_width) 405 | write_func(stream, 'hmap', hmap) 406 | write_func(stream, 'reverse', reverse) 407 | write_func(stream, 'monospaced', monospaced) 408 | write_func(stream, 'min_ch', minchar) 409 | write_func(stream, 'max_ch', maxchar) 410 | data, index = fnt.build_arrays(hmap, reverse) 411 | bw_font = ByteWriter(stream, '_font') 412 | bw_font.odata(data) 413 | bw_font.eot() 414 | bw_index = ByteWriter(stream, '_index') 415 | bw_index.odata(index) 416 | bw_index.eot() 417 | stream.write(STR02.format(minchar, minchar, maxchar, minchar, maxchar, minchar)) 418 | 419 | # BINARY OUTPUT 420 | # hmap reverse magic bytes 421 | # 0 0 0x3f 0xe7 422 | # 1 0 0x40 0xe7 423 | # 0 1 0x41 0xe7 424 | # 1 1 0x42 0xe7 425 | def write_binary_font(op_path, font_path, height, hmap, reverse): 426 | try: 427 | fnt = Font(font_path, height, 32, 126, True, None) # All chars have same width 428 | except freetype.ft_errors.FT_Exception: 429 | print("Can't open", font_path) 430 | return False 431 | sig = 1 if hmap else 0 432 | if reverse: 433 | sig += 2 434 | try: 435 | with open(op_path, 'wb') as stream: 436 | data = fnt.build_binary_array(hmap, reverse, sig) 437 | stream.write(data) 438 | except OSError: 439 | print("Can't open", op_path, 'for writing') 440 | return False 441 | return True 442 | 443 | # PARSE COMMAND LINE ARGUMENTS 444 | 445 | def quit(msg): 446 | print(msg) 447 | sys.exit(1) 448 | 449 | DESC = """font_to_py.py 450 | Utility to convert ttf or otf font files to Python source. 451 | Sample usage: 452 | font_to_py.py FreeSans.ttf 23 freesans.py 453 | 454 | This creates a font with nominal height 23 pixels with these defaults: 455 | Mapping is vertical, pitch variable, character set 32-126 inclusive. 456 | Illegal characters will be rendered as "?". 457 | 458 | To specify monospaced rendering issue: 459 | font_to_py.py FreeSans.ttf 23 --fixed freesans.py 460 | """ 461 | 462 | BINARY = """Invalid arguments. Binary (random access) font files support the standard ASCII 463 | character set (from 32 to 126 inclusive). This range cannot be overridden. 464 | Random access font files don't support an error character. 465 | """ 466 | 467 | if __name__ == "__main__": 468 | parser = argparse.ArgumentParser(__file__, description=DESC, 469 | formatter_class=argparse.RawDescriptionHelpFormatter) 470 | parser.add_argument('infile', type=str, help='Input file path') 471 | parser.add_argument('height', type=int, help='Font height in pixels') 472 | parser.add_argument('outfile', type=str, 473 | help='Path and name of output file') 474 | 475 | parser.add_argument('-x', '--xmap', action='store_true', 476 | help='Horizontal (x) mapping') 477 | parser.add_argument('-r', '--reverse', action='store_true', 478 | help='Bit reversal') 479 | parser.add_argument('-f', '--fixed', action='store_true', 480 | help='Fixed width (monospaced) font') 481 | parser.add_argument('-b', '--binary', action='store_true', 482 | help='Produce binary (random access) font file.') 483 | 484 | parser.add_argument('-s', '--smallest', 485 | type = int, 486 | default = 32, 487 | help = 'Ordinal value of smallest character default %(default)i') 488 | 489 | parser.add_argument('-l', '--largest', 490 | type = int, 491 | help = 'Ordinal value of largest character default %(default)i', 492 | default = 126) 493 | 494 | parser.add_argument('-e', '--errchar', 495 | type = int, 496 | help = 'Ordinal value of error character default %(default)i ("?")', 497 | default = 63) 498 | 499 | args = parser.parse_args() 500 | if not args.infile[0].isalpha(): 501 | quit('Font filenames must be valid Python variable names.') 502 | 503 | if not os.path.isfile(args.infile): 504 | quit("Font filename does not exist") 505 | 506 | if not os.path.splitext(args.infile)[1].upper() in ('.TTF', '.OTF'): 507 | quit("Font file should be a ttf or otf file.") 508 | 509 | if args.binary: 510 | if os.path.splitext(args.outfile)[1].upper() == '.PY': 511 | quit('Binary file must not have a .py extension.') 512 | 513 | if args.smallest != 32 or args.largest != 126 or args.errchar != ord('?'): 514 | quit(BINARY) 515 | 516 | print('Writing binary font file.') 517 | if not write_binary_font(args.outfile, args.infile, args.height, 518 | args.xmap, args.reverse): 519 | sys.exit(1) 520 | else: 521 | if not os.path.splitext(args.outfile)[1].upper() == '.PY': 522 | quit('Output filename must have a .py extension.') 523 | 524 | if args.smallest < 0: 525 | quit('--smallest must be >= 0') 526 | 527 | if args.largest > 255: 528 | quit('--largest must be < 256') 529 | 530 | if args.errchar < 0 or args.errchar > 255: 531 | quit('--errchar must be between 0 and 255') 532 | 533 | print('Writing Python font file.') 534 | if not write_font(args.outfile, args.infile, args.height, args.fixed, 535 | args.xmap, args.reverse, args.smallest, args.largest, 536 | args.errchar): 537 | sys.exit(1) 538 | 539 | print(args.outfile, 'written successfully.') 540 | 541 | -------------------------------------------------------------------------------- /game8266.py: -------------------------------------------------------------------------------- 1 | # gameESP.py for esp8266 2 | # common micropython module for ESP8266 game board designed by Billy Cheung (c) 2019 08 31 3 | # --usage-- 4 | # Using this common micropython game module, you can write micropython games to run 5 | # either on the SPI OLED or I2C OLED without chaning a line of code. 6 | # You only need to set the following line in gameESP.py file at the __init__ function 7 | # self.useSPI = True # for SPI display , with buttons read through ADC 8 | # self.useSPI = False # for I2C display, and individual hard buttons 9 | # 10 | # Note: esp8266 is very bad at running .py micropython source code files 11 | # with its very limited CPU onboard memory of 32K 12 | # so to run any program with > 300 lines of micropython codes combined (including all modules), 13 | # you need to convert source files into byte code first to avoid running out of memory. 14 | # Install a version of the mpy-cross micropython pre-compiler that can run in your system (available from github). 15 | # Type this command to convert gameESP.py to the byte code file gameESP.mpy using mpy-cross. 16 | # mpy-cross gameESP.py 17 | # then copy the gameESP.mpy file to the micropython's import directory on the flash 18 | # create your game and leaverge the functions to display, read buttons and paddle and make sounds 19 | # from the gameESP class module. 20 | # Add this line to your micropython game source code (examples attached, e.g. invader.py) 21 | # from gameESP import gameESP, Rect 22 | # g=gameESP() 23 | # 24 | # 25 | # 26 | #----------------------------------------- 27 | # SPI version of game board layout 28 | # ---------------------------------------- 29 | # micropython game hat module to use SSD1306 SPI OLED, 6 buttons and a paddle 30 | # SPI display runs 5 times faster than I2C display in micropython and you need this speeds 31 | # for games with many moving graphics (e.g. space invdader, breakout). 32 | # 33 | # Buttons are read through A0 using many resistors in a Voltage Divider circuit 34 | # ESP8266 (node MCU D1 mini) micropython 35 | # 36 | # SPI OLED 37 | # GND 38 | # VCC 39 | # D0/Sck - D5 (=GPIO14=HSCLK) 40 | # D1/MOSI- D7 (=GPIO13=HMOSI) 41 | # RES - D0 (=GPIO16) 42 | # DC - D4 (=GPIO2) 43 | # CS - Hard wired to ground. 44 | # Speaker 45 | # GPIO15  D8 Speaker 46 | # n.c. - D6 (=GPIO12=HMISO) 47 | # 48 | # The ADC(0) (aka A0) is used to read both paddles and Buttons 49 | # these two pins together control whether buttons or paddle will be read 50 | # GPIO5   D1—— PinBtn 51 | # GPIO4   D2—— pinPaddle 52 | # GPIO0 D3-- PinPaddle2 53 | 54 | # To read buttons - Pin.Btn.On() Pin.Paddle.off() Pin.Paddle2.off() 55 | # To read paddle - Pin.Btn.Off() Pin.Paddle.on() Pin.Paddle2.off() 56 | # To read paddle2 - Pin.Btn.Off() Pin.Paddle.off() Pin.Paddle2.on() 57 | 58 | # buttons are connected in series to create a voltage dividor 59 | # Each directional and A , B button when pressed will connect that point of 60 | # the voltage dividor to A0 to read the ADC value to determine which button is pressed. 61 | # resistor values are chosen to ensure we have at least a gap of 10 between each button combinations. 62 | # L, R, U, D, can be pressed individually but not toghether. 63 | # A, B, can be pressed invididually but not together. 64 | # any one of A or B, can be pressed together with any one of L,R,U,D 65 | # so you can move the gun using L,R, U,D, while shooting with A or B. 66 | # 67 | # refer to the schematics on my github for how to hook it up 68 | # 69 | # 3.3V-9K-Up-9K-Left-12K-Right-9K-Down-9K-A button-12K-B Button-9K-GND 70 | # 71 | #----------------------------------------- 72 | # I2C version of game board layout 73 | # ---------------------------------------- 74 | # mocropython game hat module to use SSD1306 I2C OLED, 6 buttons and a paddle 75 | # I2C display runs 5 times slower than I2C display in micropython. 76 | # Games with many moving graphics (e.g. space invdader, breakout) will run slower. 77 | # 78 | # Buttons are read through indvidial GPIO pins (pulled high). 79 | # Since all pins are used by buttons, only one paddle is available 80 | # no pins available for paddle2 81 | # 82 | # I2C OLED SSD1306 83 | # GPIO4 D2--- SDA OLED 84 | # GPIO5 D1--- SCL OLED 85 | # 86 | # Speaker 87 | # GPIO15  D8 Speaker 88 | # 89 | # Buttons are connect to GND when pressed 90 | # GPIO12  D6—— Left   91 | # GPIO13  D7—— Right     92 | # GPIO14  D5—— UP     93 | # GPIO2   D4——   Down     94 | # GPIO0   D3——   A 95 | # GPIO16  D0——   B 96 | # * GPIO16 cannot be pulled high by softeware, connect a 10K resisor to VCC to pull high 97 | 98 | import utime 99 | from utime import sleep_ms,ticks_ms, ticks_us, ticks_diff 100 | from machine import Pin, SPI, I2C, PWM, ADC, Timer 101 | #import ssd1306 102 | from random import getrandbits, seed 103 | # MicroPython SSD1306 OLED driver, I2C and SPI interfaces 104 | 105 | from micropython import const 106 | import framebuf 107 | 108 | 109 | # register definitions 110 | SET_CONTRAST = const(0x81) 111 | SET_ENTIRE_ON = const(0xa4) 112 | SET_NORM_INV = const(0xa6) 113 | SET_DISP = const(0xae) 114 | SET_MEM_ADDR = const(0x20) 115 | SET_COL_ADDR = const(0x21) 116 | SET_PAGE_ADDR = const(0x22) 117 | SET_DISP_START_LINE = const(0x40) 118 | SET_SEG_REMAP = const(0xa0) 119 | SET_MUX_RATIO = const(0xa8) 120 | SET_COM_OUT_DIR = const(0xc0) 121 | SET_DISP_OFFSET = const(0xd3) 122 | SET_COM_PIN_CFG = const(0xda) 123 | SET_DISP_CLK_DIV = const(0xd5) 124 | SET_PRECHARGE = const(0xd9) 125 | SET_VCOM_DESEL = const(0xdb) 126 | SET_CHARGE_PUMP = const(0x8d) 127 | 128 | # Subclassing FrameBuffer provides support for graphics primitives 129 | # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html 130 | # modified for GAMEESP to dispable use of CS. 131 | # CS pin will be reused as the 2nd paddel controller pins 132 | # 133 | class SSD1306(framebuf.FrameBuffer): 134 | def __init__(self, width, height, external_vcc): 135 | self.width = width 136 | self.height = height 137 | self.external_vcc = external_vcc 138 | self.pages = self.height // 8 139 | self.buffer = bytearray(self.pages * self.width) 140 | super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) 141 | self.init_display() 142 | 143 | def init_display(self): 144 | for cmd in ( 145 | SET_DISP | 0x00, # off 146 | # address setting 147 | SET_MEM_ADDR, 0x00, # horizontal 148 | # resolution and layout 149 | SET_DISP_START_LINE | 0x00, 150 | SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 151 | SET_MUX_RATIO, self.height - 1, 152 | SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 153 | SET_DISP_OFFSET, 0x00, 154 | SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12, 155 | # timing and driving scheme 156 | SET_DISP_CLK_DIV, 0x80, 157 | SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1, 158 | SET_VCOM_DESEL, 0x30, # 0.83*Vcc 159 | # display 160 | SET_CONTRAST, 0xff, # maximum 161 | SET_ENTIRE_ON, # output follows RAM contents 162 | SET_NORM_INV, # not inverted 163 | # charge pump 164 | SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, 165 | SET_DISP | 0x01): # on 166 | self.write_cmd(cmd) 167 | self.fill(0) 168 | self.show() 169 | 170 | def poweroff(self): 171 | self.write_cmd(SET_DISP | 0x00) 172 | 173 | def poweron(self): 174 | self.write_cmd(SET_DISP | 0x01) 175 | 176 | def contrast(self, contrast): 177 | self.write_cmd(SET_CONTRAST) 178 | self.write_cmd(contrast) 179 | 180 | def invert(self, invert): 181 | self.write_cmd(SET_NORM_INV | (invert & 1)) 182 | 183 | def show(self): 184 | x0 = 0 185 | x1 = self.width - 1 186 | if self.width == 64: 187 | # displays with width of 64 pixels are shifted by 32 188 | x0 += 32 189 | x1 += 32 190 | self.write_cmd(SET_COL_ADDR) 191 | self.write_cmd(x0) 192 | self.write_cmd(x1) 193 | self.write_cmd(SET_PAGE_ADDR) 194 | self.write_cmd(0) 195 | self.write_cmd(self.pages - 1) 196 | self.write_data(self.buffer) 197 | 198 | 199 | class SSD1306_I2C(SSD1306): 200 | def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False): 201 | self.i2c = i2c 202 | self.addr = addr 203 | self.temp = bytearray(2) 204 | self.write_list = [b'\x40', None] # Co=0, D/C#=1 205 | super().__init__(width, height, external_vcc) 206 | 207 | def write_cmd(self, cmd): 208 | self.temp[0] = 0x80 # Co=1, D/C#=0 209 | self.temp[1] = cmd 210 | self.i2c.writeto(self.addr, self.temp) 211 | 212 | def write_data(self, buf): 213 | self.write_list[1] = buf 214 | self.i2c.writevto(self.addr, self.write_list) 215 | 216 | 217 | class SSD1306_SPI(SSD1306): 218 | # def __init__(self, width, height, spi, dc, res, cs external_vcc=False): 219 | def __init__(self, width, height, spi, dc, res, external_vcc=False): 220 | 221 | self.rate = 10 * 1024 * 1024 222 | dc.init(dc.OUT, value=0) 223 | res.init(res.OUT, value=0) 224 | # cs.init(cs.OUT, value=1) 225 | self.spi = spi 226 | self.dc = dc 227 | self.res = res 228 | # self.cs = cs 229 | import time 230 | self.res(1) 231 | time.sleep_ms(1) 232 | self.res(0) 233 | time.sleep_ms(10) 234 | self.res(1) 235 | super().__init__(width, height, external_vcc) 236 | 237 | def write_cmd(self, cmd): 238 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 239 | # self.cs(1) 240 | self.dc(0) 241 | # self.cs(0) 242 | self.spi.write(bytearray([cmd])) 243 | # self.cs(1) 244 | 245 | def write_data(self, buf): 246 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 247 | # self.cs(1) 248 | self.dc(1) 249 | # self.cs(0) 250 | self.spi.write(buf) 251 | # self.cs(1) 252 | 253 | def block(self, x0, y0, x1, y1, data): 254 | """Write a block of data to display. 255 | 256 | Args: 257 | x0 (int): Starting X position. 258 | y0 (int): Starting Y position. 259 | x1 (int): Ending X position. 260 | y1 (int): Ending Y position. 261 | data (bytes): Data buffer to write. 262 | """ 263 | self.write_cmd(self.SET_COLUMN, x0, x1) 264 | self.write_cmd(self.SET_ROW, y0, y1) 265 | self.write_cmd(self.WRITE_RAM) 266 | self.write_data(data) 267 | 268 | def draw_sprite(self, buf, x, y, w, h): 269 | """Draw a sprite (optimized for horizontal drawing). 270 | 271 | Args: 272 | buf (bytearray): Buffer to draw. 273 | x (int): Starting X position. 274 | y (int): Starting Y position. 275 | w (int): Width of drawing. 276 | h (int): Height of drawing. 277 | """ 278 | x2 = x + w - 1 279 | y2 = y + h - 1 280 | if self.is_off_grid(x, y, x2, y2): 281 | return 282 | self.block(x, y, x2, y2, buf) 283 | 284 | def draw_image(self, path, x=0, y=0, w=128, h=128): 285 | """Draw image from flash. 286 | 287 | Args: 288 | path (string): Image file path. 289 | x (int): X coordinate of image left. Default is 0. 290 | y (int): Y coordinate of image top. Default is 0. 291 | w (int): Width of image. Default is 128. 292 | h (int): Height of image. Default is 128. 293 | """ 294 | x2 = x + w - 1 295 | y2 = y + h - 1 296 | if self.is_off_grid(x, y, x2, y2): 297 | return 298 | with open(path, "rb") as f: 299 | chunk_height = 1024 // w 300 | chunk_count, remainder = divmod(h, chunk_height) 301 | chunk_size = chunk_height * w * 2 302 | chunk_y = y 303 | if chunk_count: 304 | for c in range(0, chunk_count): 305 | buf = f.read(chunk_size) 306 | self.block(x, chunk_y, 307 | x2, chunk_y + chunk_height - 1, 308 | buf) 309 | chunk_y += chunk_height 310 | if remainder: 311 | buf = f.read(remainder * w * 2) 312 | self.block(x, chunk_y, 313 | x2, chunk_y + remainder - 1, 314 | buf) 315 | 316 | class gameESP(): 317 | max_vol = 6 318 | duty={0:0,1:1,2:3,3:5,4:10,5:70,6:512} 319 | tones = { 320 | ' ': 0, # silence note 321 | 'c3': 131, 322 | 'd3': 147, 323 | 'e3': 165, 324 | 'f3': 175, 325 | 'f#3': 185, 326 | 'g3': 196, 327 | 'g#3': 208, 328 | 'a3': 220, 329 | "a#3": 233, 330 | 'b3': 247, 331 | 'c4': 262, 332 | 'd4': 294, 333 | 'e4': 330, 334 | 'f4': 349, 335 | 'f#4': 370, 336 | 'g4': 392, 337 | 'g#4': 415, 338 | 'a4': 440, 339 | "a#4": 466, 340 | 'b4': 494, 341 | 'c5': 523, 342 | 'c#5': 554, 343 | 'd5': 587, 344 | 'd#5': 622, 345 | 'e5': 659, 346 | 'f5': 698, 347 | 'f#5': 740, 348 | 'g5': 784, 349 | 'g#5': 831, 350 | 'a5': 880, 351 | 'b5': 988, 352 | # note the following can only be played by ESP32, as ESP8266 can play up to 1000Hz only. 353 | 'c6': 1047, 354 | 'c#6': 1109, 355 | 'd6': 1175 356 | } 357 | 358 | 359 | def __init__(self): 360 | # True = SPI display, False = I2C display 361 | self.ESP32 = False 362 | self.paddle2 = False 363 | self.useSPI = True 364 | self.displayTimer = ticks_ms() 365 | self.vol = int(self.max_vol/2) + 1 366 | seed(ticks_us()) 367 | self.btnU = 1 << 1 368 | self.btnL = 1 << 2 369 | self.btnR = 1 << 3 370 | self.btnD = 1 << 4 371 | self.btnA = 1 << 5 372 | self.btnB = 1 << 6 373 | self.frameRate = 30 374 | self.screenW = 128 375 | self.screenH = 64 376 | self.maxBgm = 1 377 | self.bgm = 1 378 | self.songIndex = 0 379 | self.songStart = -1 380 | self.songEnd = -1 381 | self.songLoop = -3 382 | self.silence = 0 383 | self.songSpeed = 1 384 | self.timeunit = 1 385 | self.notes = False 386 | self.songBuf = [] 387 | self.Btns = 0 388 | self.lastBtns = 0 389 | self.adc = ADC(0) 390 | self.PinBuzzer = Pin(15, Pin.OUT) 391 | self.beeper = PWM(self.PinBuzzer, 500, duty=0) 392 | self.beeper2 = PWM(self.PinBuzzer, 500, duty=0) 393 | self.timerInitialized = False 394 | if self.useSPI : 395 | # configure oled display SPI SSD1306 396 | self.hspi = SPI(1, baudrate=8000000, polarity=0, phase=0) 397 | #DC, RES, CS 398 | # self.display = SSD1306_SPI(128, 64, self.hspi, Pin(2), Pin(16), Pin(0)) 399 | self.display = SSD1306_SPI(128, 64, self.hspi, Pin(2), Pin(16)) 400 | self.pinBtn = Pin(5, Pin.OUT) 401 | self.pinPaddle = Pin(4, Pin.OUT) 402 | self.paddle2 = True 403 | self.pinPaddle2 = Pin(0, Pin.OUT) 404 | 405 | else : # I2C display 406 | 407 | # configure oled display I2C SSD1306 408 | self.i2c = I2C(-1, Pin(5), Pin(4)) # SCL, SDA 409 | self.display = SSD1306_I2C(128, 64, self.i2c) 410 | self.PinBtnL = Pin(12, Pin.IN, Pin.PULL_UP) 411 | self.PinBtnR = Pin(13, Pin.IN, Pin.PULL_UP) 412 | self.PinBtnU = Pin(14, Pin.IN, Pin.PULL_UP) 413 | self.PinBtnD = Pin(2, Pin.IN, Pin.PULL_UP) 414 | self.PinBtnA = Pin(0, Pin.IN, Pin.PULL_UP) 415 | self.PinBtnB = Pin(16, Pin.IN) #GPIO 16 always pull down cannot pull up 416 | 417 | def deinit(self) : 418 | self.beeper.deinit() 419 | self.beeper2.deinit() 420 | self.songIndex = 0 421 | if self.timerInitialized : 422 | self.timer.deinit() 423 | 424 | def getPaddle (self) : 425 | if self.useSPI : 426 | self.pinPaddle.on() 427 | self.pinPaddle2.off() 428 | self.pinBtn.off() 429 | sleep_ms(1) 430 | return self.adc.read() 431 | 432 | def getPaddle2 (self) : 433 | if self.useSPI : 434 | self.pinPaddle2.on() 435 | self.pinPaddle.off() 436 | self.pinBtn.off() 437 | sleep_ms(1) 438 | return self.adc.read() 439 | 440 | def pressed (self,btn) : 441 | return (self.Btns & btn) 442 | 443 | def justPressed (self,btn) : 444 | return (self.Btns & btn) and not (self.lastBtns & btn) 445 | 446 | def justReleased (self,btn) : 447 | return (self.lastBtns & btn) and not (self.Btns & btn) 448 | 449 | def getBtn(self) : 450 | self.lastBtns = self.Btns 451 | self.Btns = 0 452 | if self.useSPI : 453 | # SPI board, record each key pressed based on the ADC value 454 | self.pinPaddle.off() 455 | self.pinPaddle2.off() 456 | self.pinBtn.on() 457 | 458 | a0=self.adc.read() 459 | if a0 < 570 : 460 | if a0 < 362 : 461 | if a0 > 176 : 462 | if a0 > 277 : 463 | self.Btns |= self.btnU | self.btnA 464 | elif a0 > 241 : 465 | self.Btns |= self.btnL 466 | else : 467 | self.Btns |= self.btnU | self.btnD 468 | elif a0 > 68: 469 | self.Btns |= self.btnU 470 | else : 471 | if a0 > 485 : 472 | if a0 > 531 : 473 | self.Btns |= self.btnD 474 | else : 475 | self.Btns |= self.btnU | self.btnB 476 | else: 477 | if a0 > 443 : 478 | self.Btns |= self.btnL | self.btnA 479 | else : 480 | self.Btns |= self.btnR 481 | else: 482 | if a0 < 737 : 483 | if a0 < 661 : 484 | if a0 > 615 : 485 | self.Btns |= self.btnD | self.btnA 486 | else : 487 | self.Btns |= self.btnR | self.btnA 488 | elif a0 > 683 : 489 | self.Btns |= self.btnA 490 | else : 491 | self.Btns |= self.btnL | self.btnB 492 | elif a0 < 841 : 493 | if a0 > 805 : 494 | self.Btns |= self.btnD | self.btnB 495 | else : 496 | self.Btns |= self.btnR | self.btnB 497 | elif a0 > 870 : 498 | self.Btns |= self.btnB 499 | else : 500 | self.Btns |= self.btnA | self.btnB 501 | 502 | else : # I2C board, read buttons directly 503 | self.Btns = self.Btns | (not self.PinBtnU.value()) << 1 | (not self.PinBtnL.value()) << 2 | (not self.PinBtnR.value()) << 3 | (not self.PinBtnD.value()) << 4 | (not self.PinBtnA.value()) << 5 | (not self.PinBtnB.value())<< 6 504 | return self.Btns 505 | print (self.Btns) 506 | 507 | def setVol(self) : 508 | if self.pressed(self.btnB): 509 | if self.justPressed(self.btnU) : 510 | self.vol= min (self.vol+1, self.max_vol) 511 | self.playTone('c4', 100) 512 | return True 513 | elif self.justPressed(self.btnD) : 514 | self.vol= max (self.vol-1, 0) 515 | self.playTone('d4', 100) 516 | return True 517 | 518 | return False 519 | 520 | def setFrameRate(self) : 521 | 522 | if self.justPressed(self.btnR) : 523 | self.frameRate = self.frameRate + 5 if self.frameRate < 120 else 5 524 | self.playTone('e4', 100) 525 | return True 526 | elif self.pressed(self.btnB) and self.justPressed(self.btnR) : 527 | self.frameRate = self.frameRate - 5 if self.frameRate > 5 else 120 528 | self.playTone('f4', 100) 529 | return True 530 | return False 531 | 532 | def playTone(self, tone, tone_duration, rest_duration=0): 533 | beeper = PWM(self.PinBuzzer, freq=self.tones[tone], duty=self.duty[self.vol]) 534 | sleep_ms(tone_duration) 535 | beeper.deinit() 536 | sleep_ms(rest_duration) 537 | 538 | def playSound(self, freq, tone_duration, rest_duration=0): 539 | beeper = PWM(self.PinBuzzer, freq, duty=self.duty[self.vol]) 540 | sleep_ms(tone_duration) 541 | beeper.deinit() 542 | sleep_ms(rest_duration) 543 | 544 | 545 | def handleInterrupt(self,timer): 546 | self.beeper2.deinit() # note has been played logn enough, now stop sound 547 | 548 | if self.songBuf[self.songIndex] == self.songLoop : 549 | self.songIndex = 3 # repeat from first note 550 | 551 | if self.songBuf[self.songIndex] != self.songEnd : 552 | if self.songBuf[self.songIndex] == 0 : 553 | self.beeper2 = PWM(self.PinBuzzer, 100,0) 554 | elif self.notes : 555 | self.beeper2 = PWM(self.PinBuzzer, self.tones[self.songBuf[self.songIndex]], self.duty[self.vol]) 556 | else : 557 | self.beeper2 = PWM(self.PinBuzzer, self.songBuf[self.songIndex], self.duty[self.vol]) 558 | self.timer.init(period=int(self.songBuf[self.songIndex+1] * self.timeunit * self.songSpeed) , mode=Timer.ONE_SHOT, callback=self.handleInterrupt) 559 | self.songIndex +=2 560 | 561 | def startSong(self, songBuf=None): 562 | if self.bgm : 563 | if songBuf != None : 564 | self.songBuf = songBuf 565 | if self.songBuf[0] != self.songStart : 566 | print ("Cannot start Song, Invalid songBuf") 567 | return False 568 | self.notes = self.songBuf[1] 569 | self.timeunit = self.songBuf[2] 570 | self.songIndex = 3 571 | if not self.timerInitialized : 572 | self.timerInitialized = True 573 | self.timer = Timer(1) 574 | self.timer.init(period=100 , mode=Timer.ONE_SHOT, callback=self.handleInterrupt) 575 | 576 | def stopSong(self): 577 | self.songIndex = 0 578 | 579 | def random (self, x, y) : 580 | return getrandbits(20) % (y-x+1) + x 581 | 582 | def display_and_wait(self) : 583 | self.display.show() 584 | timer_dif = int(1000/self.frameRate) - ticks_diff(ticks_ms(), self.displayTimer) 585 | if timer_dif > 0 : 586 | sleep_ms(timer_dif) 587 | self.displayTimer=ticks_ms() 588 | 589 | class Rect (object): 590 | def __init__(self, x, y, w, h): 591 | self.x = x 592 | self.y = y 593 | self.w = w 594 | self.h = h 595 | 596 | 597 | def move (self, vx, vy) : 598 | self.x = self.x + vx 599 | self.y = self.y + vy 600 | 601 | 602 | def colliderect (self, rect1) : 603 | if (self.x + self.w > rect1.x and 604 | self.x < rect1.x + rect1.w and 605 | self.y + self.h > rect1.y and 606 | self.y < rect1.y + rect1.h) : 607 | return True 608 | else: 609 | return False 610 | --------------------------------------------------------------------------------