From 7541d9b258e4d0c115d6160592ac7e17f8fd50cc Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 27 Apr 2018 15:05:21 -0400 Subject: [PATCH] - add space invaders example Change-Id: I439b6012af8c2bd8f555744657b8091ac168242b --- doc/build/orm/examples.rst | 7 + doc/build/orm/space_invaders.jpg | Bin 0 -> 58066 bytes examples/space_invaders/__init__.py | 24 + examples/space_invaders/space_invaders.py | 754 ++++++++++++++++++++++ 4 files changed, 785 insertions(+) create mode 100644 doc/build/orm/space_invaders.jpg create mode 100644 examples/space_invaders/__init__.py create mode 100644 examples/space_invaders/space_invaders.py diff --git a/doc/build/orm/examples.rst b/doc/build/orm/examples.rst index 76afab26f1..3dd81f856b 100644 --- a/doc/build/orm/examples.rst +++ b/doc/build/orm/examples.rst @@ -76,6 +76,13 @@ Relationship Join Conditions .. automodule:: examples.join_conditions +.. _examples_spaceinvaders: + +Space Invaders +-------------- + +.. automodule:: examples.space_invaders + .. _examples_xmlpersistence: XML Persistence diff --git a/doc/build/orm/space_invaders.jpg b/doc/build/orm/space_invaders.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dfee39dc5807b4ec2e184b51eec971911c787098 GIT binary patch literal 58066 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�YM~(D$3hmLB9D#Asm|TeFTHP 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 literal 0 Hc-jL100001 diff --git a/examples/space_invaders/__init__.py b/examples/space_invaders/__init__.py new file mode 100644 index 0000000000..8816045dc3 --- /dev/null +++ b/examples/space_invaders/__init__.py @@ -0,0 +1,24 @@ +""" +A Space Invaders game using SQLite as the state machine. + +Originally developed in 2012. Adapted to work in Python 3. + +Runs in a textual console using ASCII art. + + +.. image:: space_invaders.jpg + + +To run:: + + python -m examples.space_invaders.space_invaders + +While it runs, watch the SQL output in the log:: + + tail -f space_invaders.log + +enjoy! + +.. autosource:: + +""" \ No newline at end of file diff --git a/examples/space_invaders/space_invaders.py b/examples/space_invaders/space_invaders.py new file mode 100644 index 0000000000..3ce280aece --- /dev/null +++ b/examples/space_invaders/space_invaders.py @@ -0,0 +1,754 @@ +import sys +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import create_engine, Integer, Column, ForeignKey, \ + String, func +from sqlalchemy.orm import relationship, Session, joinedload +from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method +import curses +import time +import textwrap +import re +import random +import logging + +_PY3 = sys.version_info > (3, 0) +if _PY3: + xrange = range + + +logging.basicConfig( + filename="space_invaders.log", + format="%(asctime)s,%(msecs)03d %(levelname)-5.5s %(message)s" +) +logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) + +Base = declarative_base() + +WINDOW_LEFT = 10 +WINDOW_TOP = 2 +WINDOW_WIDTH = 70 +WINDOW_HEIGHT = 34 +VERT_PADDING = 2 +HORIZ_PADDING = 5 +ENEMY_VERT_SPACING = 4 +MAX_X = WINDOW_WIDTH - HORIZ_PADDING +MAX_Y = WINDOW_HEIGHT - VERT_PADDING +LEFT_KEY = ord("j") +RIGHT_KEY = ord("l") +FIRE_KEY = ord(" ") +PAUSE_KEY = ord("p") + +COLOR_MAP = { + "K": curses.COLOR_BLACK, + "R": curses.COLOR_RED, + "B": curses.COLOR_BLUE, + "C": curses.COLOR_CYAN, + "G": curses.COLOR_GREEN, + "M": curses.COLOR_MAGENTA, + "R": curses.COLOR_RED, + "W": curses.COLOR_WHITE, + "Y": curses.COLOR_YELLOW +} + + +class Glyph(Base): + """Describe a "glyph", a graphical element + to be painted on the screen. + + """ + __tablename__ = 'glyph' + id = Column(Integer, primary_key=True) + name = Column(String) + type = Column(String) + width = Column(Integer) + height = Column(Integer) + data = Column(String) + alt_data = Column(String) + __mapper_args__ = {"polymorphic_on": type} + + def __init__(self, name, img, alt=None): + self.name = name + self.data, self.width, self.height = \ + self._encode_glyph(img) + if alt is not None: + self.alt_data, alt_w, alt_h = \ + self._encode_glyph(alt) + + def _encode_glyph(self, img): + """Receive a textual description of the glyph and + encode into a format understood by + GlyphCoordinate.render(). + + """ + img = re.sub(r'^\n', "", textwrap.dedent(img)) + color = "W" + lines = [line.rstrip() for line in img.split("\n")] + data = [] + for line in lines: + render_line = [] + line = list(line) + while line: + char = line.pop(0) + if char == '#': + color = line.pop(0) + continue + render_line.append((color, char)) + data.append(render_line) + width = max([len(rl) for rl in data]) + data = "".join( + "".join("%s%s" % (color, char) for color, char in render_line) + + ("W " * (width - len(render_line))) + for render_line in data + ) + return data, width, len(lines) + + def glyph_for_state(self, coord, state): + """Return the appropriate data representation + for this Glyph, based on the current coordinates + and state. + + Subclasses may override this to provide animations. + + """ + return self.data + + +class GlyphCoordinate(Base): + """Describe a glyph rendered at a certain x, y coordinate. + + The GlyphCoordinate may also include optional values + such as the tick at time of render, a label, and a + score value. + + """ + __tablename__ = 'glyph_coordinate' + id = Column(Integer, primary_key=True) + glyph_id = Column(Integer, ForeignKey('glyph.id')) + x = Column(Integer) + y = Column(Integer) + tick = Column(Integer) + label = Column(String) + score = Column(Integer) + glyph = relationship(Glyph, innerjoin=True) + + def __init__( + self, + session, glyph_name, x, y, + tick=None, label=None, score=None): + self.glyph = session.query(Glyph).\ + filter_by(name=glyph_name).one() + self.x = x + self.y = y + self.tick = tick + self.label = label + self.score = score + session.add(self) + + def render(self, window, state): + """Render the Glyph at this position.""" + + col = 0 + row = 0 + glyph = self.glyph + data = glyph.glyph_for_state(self, state) + for color, char in [ + (data[i], data[i + 1]) + for i in xrange(0, len(data), 2) + ]: + + x = self.x + col + y = self.y + row + if 0 <= x <= MAX_X and 0 <= y <= MAX_Y: + window.addstr( + y + VERT_PADDING, + x + HORIZ_PADDING, + char, + _COLOR_PAIRS[color]) + col += 1 + if col == glyph.width: + col = 0 + row += 1 + if self.label: + self._render_label(window, False) + + def _render_label(self, window, blank): + label = self.label if not blank else " " * len(self.label) + if self.x + self.width + len(self.label) < MAX_X: + window.addstr(self.y, self.x + self.width, label) + else: + window.addstr(self.y, self.x - len(self.label), label) + + def blank(self, window): + """Render a blank box for this glyph's position and size.""" + + glyph = self.glyph + x = min(max(self.x, 0), MAX_X) + width = min(glyph.width, MAX_X - x) or 1 + for y_a in xrange(self.y, self.y + glyph.height): + y = y_a + window.addstr( + y + VERT_PADDING, + x + HORIZ_PADDING, + " " * width) + + if self.label: + self._render_label(window, True) + + @hybrid_property + def width(self): + return self.glyph.width + + @width.expression + def width(cls): + return Glyph.width + + @hybrid_property + def height(self): + return self.glyph.height + + @height.expression + def height(cls): + return Glyph.height + + @hybrid_property + def bottom_bound(self): + return self.y + self.height >= MAX_Y + + @hybrid_property + def top_bound(self): + return self.y <= 0 + + @hybrid_property + def left_bound(self): + return self.x <= 0 + + @hybrid_property + def right_bound(self): + return self.x + self.width >= MAX_X + + @hybrid_property + def right_edge_bound(self): + return self.x > MAX_X + + @hybrid_method + def intersects(self, other): + """Return True if this GlyphCoordinate intersects with + the given GlyphCoordinate.""" + + return ~( + (self.x + self.width < other.x) | + (self.x > other.x + other.width) + ) & ~( + (self.y + self.height < other.y) | + (self.y > other.y + other.height) + ) + + +class EnemyGlyph(Glyph): + """Describe an enemy.""" + __mapper_args__ = {"polymorphic_identity": "enemy"} + + +class ArmyGlyph(EnemyGlyph): + """Describe an enemy that's part of the "army". """ + __mapper_args__ = {"polymorphic_identity": "army"} + + def glyph_for_state(self, coord, state): + if state["flip"]: + return self.alt_data + else: + return self.data + + +class SaucerGlyph(EnemyGlyph): + """Describe the enemy saucer flying overhead.""" + __mapper_args__ = {"polymorphic_identity": "saucer"} + + def glyph_for_state(self, coord, state): + if state["flip"] == 0: + return self.alt_data + else: + return self.data + + +class MessageGlyph(Glyph): + """Describe a glyph for displaying a message.""" + __mapper_args__ = {"polymorphic_identity": "message"} + + +class PlayerGlyph(Glyph): + """Describe a glyph representing the player.""" + __mapper_args__ = {"polymorphic_identity": "player"} + + +class MissileGlyph(Glyph): + """Describe a glyph representing a missile.""" + __mapper_args__ = {"polymorphic_identity": "missile"} + + +class SplatGlyph(Glyph): + """Describe a glyph representing a "splat".""" + __mapper_args__ = {"polymorphic_identity": "splat"} + + def glyph_for_state(self, coord, state): + age = state["tick"] - coord.tick + if age > 5: + return self.alt_data + else: + return self.data + + +def init_glyph(session): + """Create the glyphs used during play.""" + + enemy1 = ArmyGlyph( + "enemy1", """ + #W-#B^#R-#B^#W- + #G| | + """, + """ + #W>#B^#R-#B^#W< + #G^ ^ + """ + ) + + enemy2 = ArmyGlyph( + "enemy2", """ + #W*** + #R<#C~~~#R> + """, + """ + #W@@@ + #R<#C---#R> + """ + ) + + enemy3 = ArmyGlyph( + "enemy3", """ + #Y((--)) + #M-~-~-~ + """, + """ + #Y[[--]] + #M~-~-~- + """ + ) + + saucer = SaucerGlyph( + "saucer", + """#R~#Y^#R~#G<<((=#WOO#G=))>>""", + """#Y^#R~#Y^#G<<((=#WOO#G=))>>""", + ) + + splat1 = SplatGlyph( + "splat1", + """ + #WVVVVV + #W> #R*** #W< + #W^^^^^ + """, + """ + #M| + #M- #Y+++ #M- + #M| + """ + ) + + ship = PlayerGlyph("ship", """ + #Y^ + #G===== + """) + + missile = MissileGlyph("missile", """ + | + """) + + start = MessageGlyph( + "start_message", + "J = move left; L = move right; SPACE = fire\n" + " #GPress any key to start") + lose = MessageGlyph("lose_message", + "#YY O U L O S E ! ! !") + win = MessageGlyph( + "win_message", + "#RL E V E L C L E A R E D ! ! !" + ) + paused = MessageGlyph( + "pause_message", + "#WP A U S E D\n#GPress P to continue") + session.add_all( + [enemy1, enemy2, enemy3, ship, saucer, + missile, start, lose, win, + paused, splat1]) + + +def setup_curses(): + """Setup terminal/curses state.""" + + window = curses.initscr() + curses.noecho() + + window = curses.newwin( + WINDOW_HEIGHT + (VERT_PADDING * 2), + WINDOW_WIDTH + (HORIZ_PADDING * 2), + WINDOW_TOP - VERT_PADDING, + WINDOW_LEFT - HORIZ_PADDING) + curses.start_color() + + global _COLOR_PAIRS + _COLOR_PAIRS = {} + for i, (k, v) in enumerate(COLOR_MAP.items(), 1): + curses.init_pair(i, v, curses.COLOR_BLACK) + _COLOR_PAIRS[k] = curses.color_pair(i) + return window + + +def init_positions(session): + """Establish a new field of play. + + This generates GlyphCoordinate objects + and persists them to the database. + + """ + + # delete all existing coordinates + session.query(GlyphCoordinate).delete() + + session.add( + GlyphCoordinate( + session, "ship", + WINDOW_WIDTH // 2 - 2, + WINDOW_HEIGHT - 4) + ) + + arrangement = ( + ("enemy3", 50), ("enemy2", 25), + ("enemy1", 10), ("enemy2", 25), + ("enemy1", 10)) + for (ship_vert, (etype, score)) in zip( + xrange(5, 30, ENEMY_VERT_SPACING), arrangement): + for ship_horiz in xrange(0, 50, 10): + session.add( + GlyphCoordinate( + session, etype, + ship_horiz, + ship_vert, + score=score) + ) + + +def draw(session, window, state): + """Load all current GlyphCoordinate objects from the + database and render. + + """ + for gcoord in session.query(GlyphCoordinate).\ + options(joinedload("glyph")): + gcoord.render(window, state) + window.addstr( + 1, WINDOW_WIDTH - 5, + "Score: %.4d" % state['score']) + window.move(0, 0) + window.refresh() + + +def check_win(session, state): + """Return the number of army glyphs remaining - + the player wins if this is zero.""" + + return session.query( + func.count(GlyphCoordinate.id) + ).join( + GlyphCoordinate.glyph.of_type(ArmyGlyph) + ).scalar() + + +def check_lose(session, state): + """Return the number of army glyphs either colliding + with the player or hitting the bottom of the screen. + + The player loses if this is non-zero.""" + + player = state["player"] + return session.query(GlyphCoordinate).join( + GlyphCoordinate.glyph.of_type(ArmyGlyph) + ).filter( + GlyphCoordinate.intersects(player) | + GlyphCoordinate.bottom_bound + ).count() + + +def render_message(session, window, msg, x, y): + """Render a message glyph. + + Clears the area beneath the message first + and assumes the display will be paused + afterwards. + + """ + # create message box + msg = GlyphCoordinate(session, msg, x, y) + + # clear existing glyphs which intersect + for gly in session.query(GlyphCoordinate).join( + GlyphCoordinate.glyph + ).filter(GlyphCoordinate.intersects(msg)): + gly.blank(window) + + # render + msg.render(window, {}) + window.refresh() + return msg + + +def win(session, window, state): + """Handle the win case.""" + render_message(session, window, "win_message", 15, 15) + time.sleep(2) + start(session, window, state, True) + + +def lose(session, window, state): + """Handle the lose case.""" + render_message(session, window, "lose_message", 15, 15) + time.sleep(2) + start(session, window, state) + + +def pause(session, window, state): + """Pause the game.""" + msg = render_message(session, window, "pause_message", 15, 15) + prompt(window) + msg.blank(window) + session.delete(msg) + + +def prompt(window): + """Display a prompt, quashing any keystrokes + which might have remained.""" + + window.move(0, 0) + window.nodelay(1) + window.getch() + window.nodelay(0) + window.getch() + window.nodelay(1) + + +def move_army(session, window, state): + """Update the army position based on the current + size of the field.""" + speed = 30 // 25 * state["num_enemies"] + + flip = (state["tick"] % speed) == 0 + + if not flip: + return + else: + state["flip"] = not state["flip"] + + x_slide = 1 + + # get the lower/upper boundaries of the army + # along the X axis. + min_x, max_x = session.query( + func.min(GlyphCoordinate.x), + func.max(GlyphCoordinate.x + GlyphCoordinate.width), + ).join( + GlyphCoordinate.glyph.of_type(ArmyGlyph) + ).first() + + if min_x is None or max_x is None: + # no enemies + return + + direction = state["army_direction"] + move_y = False + if direction == 0 and max_x + x_slide >= MAX_X: + direction = state["army_direction"] = 1 + move_y = True + elif direction == 1 and min_x - x_slide <= 0: + direction = state["army_direction"] = 0 + move_y = True + + for enemy_g in session.query(GlyphCoordinate).join( + GlyphCoordinate.glyph.of_type(ArmyGlyph) + ): + enemy_g.blank(window) + + if move_y: + enemy_g.y += 1 + elif direction == 0: + enemy_g.x += x_slide + elif direction == 1: + enemy_g.x -= x_slide + + +def move_player(session, window, state): + """Receive player input and adjust state.""" + + ch = window.getch() + if ch not in (LEFT_KEY, RIGHT_KEY, FIRE_KEY, PAUSE_KEY): + return + elif ch == PAUSE_KEY: + pause(session, window, state) + return + + player = state["player"] + if ch == RIGHT_KEY and not player.right_bound: + player.blank(window) + player.x += 1 + elif ch == LEFT_KEY and not player.left_bound: + player.blank(window) + player.x -= 1 + elif ch == FIRE_KEY and state["missile"] is None: + state["missile"] = GlyphCoordinate( + session, + "missile", + player.x + 3, + player.y - 1) + + +def move_missile(session, window, state): + """Update the status of the current missile, if any.""" + + if state["missile"] is None or \ + state["tick"] % 2 != 0: + return + + missile = state["missile"] + + # locate enemy glyphs which intersect with the + # missile's current position; i.e. a hit + glyph = session.query(GlyphCoordinate).\ + join(GlyphCoordinate.glyph.of_type(EnemyGlyph)).\ + filter(GlyphCoordinate.intersects(missile)).\ + first() + missile.blank(window) + if glyph or missile.top_bound: + # missle is done + session.delete(missile) + state["missile"] = None + if glyph: + # score! + score(session, window, state, glyph) + else: + # move missle up one character. + missile.y -= 1 + + +def move_saucer(session, window, state): + """Update the status of the saucer.""" + + saucer_interval = 500 + saucer_speed_interval = 4 + if state["saucer"] is None and \ + state["tick"] % saucer_interval != 0: + return + + if state["saucer"] is None: + state["saucer"] = saucer = GlyphCoordinate( + session, + "saucer", -6, 1, + score=random.randrange(100, 600, 100)) + elif state["tick"] % saucer_speed_interval == 0: + saucer = state["saucer"] + saucer.blank(window) + saucer.x += 1 + if saucer.right_edge_bound: + session.delete(saucer) + state["saucer"] = None + + +def update_splat(session, window, state): + """Render splat animations.""" + + for splat in session.query(GlyphCoordinate).\ + join(GlyphCoordinate.glyph.of_type(SplatGlyph)): + age = state["tick"] - splat.tick + if age > 10: + splat.blank(window) + session.delete(splat) + else: + splat.render(window, state) + + +def score(session, window, state, glyph): + """Process a glyph intersecting with a missile.""" + + glyph.blank(window) + session.delete(glyph) + if state["saucer"] is glyph: + state["saucer"] = None + state["score"] += glyph.score + # render a splat ! + GlyphCoordinate( + session, "splat1", glyph.x, glyph.y, + tick=state["tick"], label=str(glyph.score)) + + +def update_state(session, window, state): + """Update all state for each game tick.""" + + num_enemies = state["num_enemies"] = check_win(session, state) + if num_enemies == 0: + win(session, window, state) + elif check_lose(session, state): + lose(session, window, state) + else: + # update the tick counter. + state["tick"] += 1 + move_player(session, window, state) + move_missile(session, window, state) + move_army(session, window, state) + move_saucer(session, window, state) + update_splat(session, window, state) + + +def start(session, window, state, continue_=False): + """Start a new field of play.""" + + render_message(session, window, "start_message", 15, 20) + prompt(window) + + init_positions(session) + + player = session.query(GlyphCoordinate).join( + GlyphCoordinate.glyph.of_type(PlayerGlyph) + ).one() + state.update({ + "field_pos": 0, + "alt": False, + "tick": 0, + "missile": None, + "saucer": None, + "player": player, + "army_direction": 0, + "flip": False + }) + if not continue_: + state["score"] = 0 + + window.clear() + window.box() + draw(session, window, state) + + +def main(): + """Initialize the database and establish the game loop.""" + + e = create_engine("sqlite://") + Base.metadata.create_all(e) + session = Session(e) + init_glyph(session) + session.commit() + window = setup_curses() + state = {} + start(session, window, state) + while True: + update_state(session, window, state) + draw(session, window, state) + time.sleep(.01) + +if __name__ == '__main__': + main() -- 2.47.2