├── requirements.txt ├── .gitignore ├── Agave-Regular.ttf ├── src ├── run_arcade_mode.py └── core │ ├── buffers.py │ ├── diffs.py │ ├── constants.py │ ├── modes.py │ ├── games.py │ ├── puzzles.py │ └── spawners │ └── spawners.py ├── notes └── Notes.md └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | pynvim 2 | pygame -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | ..* 3 | .idea/ 4 | Untitled 5 | *.un~ 6 | -------------------------------------------------------------------------------- /Agave-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaphaelKMandel/chronicles-of-vimia/HEAD/Agave-Regular.ttf -------------------------------------------------------------------------------- /src/run_arcade_mode.py: -------------------------------------------------------------------------------- 1 | from core.games import Game 2 | from core.spawners.spawners import * 3 | 4 | game = Game() 5 | game.spawner = RandomSpawner(game) 6 | # game.spawner.spawners = [MovementSpawner] 7 | game.run() 8 | -------------------------------------------------------------------------------- /notes/Notes.md: -------------------------------------------------------------------------------- 1 | # Tasks 2 | 3 | - [ ] Home screen with commands 4 | - [ ] Arcade vs Campaign mode 5 | - [ ] Way to remove certain keybinds 6 | 7 | # Approach 8 | 9 | ## Modes 10 | 11 | - Modes are taken from the nvim child process 12 | - Each mode has regular expressions to determine if pending commands are being issued to prevent game freezing 13 | 14 | ## Buffers 15 | 16 | - Simple class to automate process of creating and manipulating buffers in the nvim child process 17 | 18 | ## Puzzle 19 | 20 | - Contain a buffer 21 | - Determine completion criteria e.g. lines must equal some target 22 | - Contain logic for moving and drawing the buffer 23 | 24 | ## Spawners 25 | 26 | - Classes to spawn puzzles 27 | -------------------------------------------------------------------------------- /src/core/buffers.py: -------------------------------------------------------------------------------- 1 | from .constants import * 2 | 3 | 4 | class Buffer: 5 | def __init__(self, lines): 6 | NVIM.command("enew") 7 | self.buffer = NVIM.current.buffer 8 | self.no = self.buffer.number 9 | NVIM.command(f"file /temp/{self.no}") 10 | NVIM.command("setlocal undolevels=-1") 11 | self.buffer[:] = lines 12 | NVIM.input("") 13 | NVIM.command("setlocal nomodified") 14 | NVIM.command("setlocal undolevels=100") 15 | 16 | @property 17 | def lines(self): 18 | return NVIM.current.buffer[:] 19 | 20 | def get_pos(self): 21 | return NVIM.funcs.getpos('.') 22 | 23 | @property 24 | def row(self): 25 | return self.get_pos()[1] 26 | 27 | @property 28 | def col(self): 29 | return self.get_pos()[2] 30 | 31 | @property 32 | def cols(self): 33 | return max([len(line) for line in self.lines]) 34 | 35 | @property 36 | def rows(self): 37 | return len(self.lines) 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/core/diffs.py: -------------------------------------------------------------------------------- 1 | from difflib import SequenceMatcher 2 | 3 | SPACE = "_" 4 | 5 | 6 | def replace(string): 7 | return string.replace(" ", SPACE) 8 | 9 | 10 | def get_diff(current, target): 11 | words = [] 12 | x = SequenceMatcher(a=current, b=target).get_opcodes() 13 | for op, s1, f1, s2, f2 in x: 14 | # print(op, s1, f1, s2, f2) 15 | if op == "replace": 16 | words.append( 17 | ("delete", replace(current[s1:f1])) 18 | ) 19 | words.append( 20 | ("insert", target[s2:f2]) 21 | ) 22 | elif op == "insert": 23 | words.append( 24 | (op, replace(target[s2:f2])) 25 | ) 26 | 27 | elif op == "delete": 28 | words.append( 29 | (op, replace(current[s1:f1])) 30 | ) 31 | 32 | else: 33 | words.append( 34 | (op, current[s1:f1]) 35 | ) 36 | 37 | return words 38 | 39 | 40 | if __name__ == "__main__": 41 | current = "def foo(self, x): " 42 | target = "def foo(self):" 43 | print(get_diff(current, target)) 44 | -------------------------------------------------------------------------------- /src/core/constants.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import pynvim 3 | 4 | # Initialize pynvim 5 | NVIM = pynvim.attach('child', argv=["nvim", "--embed", "--headless", "--clean"]) 6 | NVIM.command("set noswapfile") 7 | 8 | # Initialize pygame 9 | pygame.init() 10 | pygame.display.set_caption("Chronicles of Vimia") 11 | 12 | # Repeat keys 13 | pygame.key.set_repeat(600, 50) 14 | 15 | # Game dimensions 16 | WIDTH, HEIGHT = 1600, 900 17 | FPS = 60 18 | FONT_SIZE = 32 19 | SCREEN = pygame.display.set_mode((WIDTH, HEIGHT)) 20 | BOTTOM = 5 21 | 22 | # Game Objects 23 | CLOCK = pygame.time.Clock() 24 | 25 | # Colors 26 | WHITE = (255, 255, 255) 27 | GREEN = (0, 255, 0) 28 | RED = (255, 0, 0) 29 | ORANGE = (200, 165, 0) 30 | TEXT_COLOR = (255, 255, 255) 31 | BACKGROUND_COLOR = (0, 0, 0) 32 | CURSOR_COLOR = (255, 255, 255) 33 | CURSOR_TEXT_COLOR = (0, 0, 0) # Text color when under cursor 34 | WORD_BACKGROUND_COLOR = (32, 0, 106) 35 | 36 | # Font 37 | FONT = pygame.font.SysFont("monospace", FONT_SIZE) 38 | FONTS = pygame.font.get_fonts() 39 | for font in {"agave", "agaveregular"}: 40 | if font in FONTS: 41 | FONT = pygame.font.SysFont(font, FONT_SIZE) 42 | break 43 | 44 | CHAR_WIDTH, CHAR_HEIGHT = FONT.size(" ") 45 | 46 | 47 | def get_pos(): 48 | return NVIM.funcs.getpos(".") 49 | 50 | def draw_text(text, x, y, color): 51 | text_surface = FONT.render(text, True, color) 52 | text_rect = text_surface.get_rect() 53 | text_rect.topleft = (x, y) 54 | SCREEN.blit(text_surface, text_rect) 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chronicles of Vimia (WIP) 2 | 3 | A typing-tutor-style game where the user uses Vim motions to edit falling text strings before they hit the ground. 4 | The game helps you practice Vim commands in a fun, interactive way. 5 | This game focuses on using Vim motions to quickly edit text, rather than presenting puzzles to study. 6 | As you progress and get better, using fewer keystrokes should happen naturally, and your scores will be higher. 7 | 8 | Example Video(https://www.youtube.com/watch?v=yq6urNPjYEA) 9 | 10 | This is a work in progress, so please forgive any bugs or missing features. 11 | 12 | ## Setup 13 | 14 | 1. Install the bundled monospace Agave Nerd Font (Optional) 15 | 16 | 2. Install the required python dependencies: 17 | 18 | ```bash 19 | pip install -r requirements.txt 20 | ``` 21 | 22 | 3. Run the game: 23 | 24 | ```bash 25 | python3 src/run_arcade_mode.py 26 | ``` 27 | 28 | ## How to Play 29 | 30 | - Use Vim motions to move around in the falling buffers 31 | - Use Vim commands to delete text in red and insert mode to add text in green 32 | - Score points by correctly typing and editing the falling text before it hits the ground 33 | - Your keystrokes are counted so use as few keystrokes as possible to get the highest score 34 | - Don't let the buffers hit the ground, or they will disappear! 35 | - Quit the game using :q 36 | - Start a new game using :n 37 | 38 | ## Current Features 39 | 40 | 1. An Arcade mode, where users shoot for the highest score 41 | 42 | ## Planned Features 43 | 44 | 1. A campaign mode, where users can level up by acquiring motions; fight bosses (multiline puzzles?) at end 45 | 2. A training mode, where puzzles are generated to get practice on a certain motion or command 46 | 3. More than one falling buffer at a time 47 | 4. Better graphics, effects, etc... 48 | 5. A larger library of buffer spawners to teach certain chords/patterns 49 | 6. Adding new spawners as score increases 50 | -------------------------------------------------------------------------------- /src/core/modes.py: -------------------------------------------------------------------------------- 1 | from re import match 2 | 3 | from .constants import * 4 | 5 | 6 | class VimMode: 7 | def __init__(self, game): 8 | self.game = game 9 | 10 | def is_pending(self, command): 11 | return False 12 | 13 | 14 | class NormalMode(VimMode): 15 | NAME = "NORMAL" 16 | COUNT = "([1-9][0-9]*)" 17 | 18 | def draw(self, items): 19 | for char, x, y in items: 20 | pygame.draw.rect(SCREEN, CURSOR_COLOR, (x, y, CHAR_WIDTH, CHAR_HEIGHT)) 21 | draw_text(char, x, y, CURSOR_TEXT_COLOR) 22 | 23 | def is_count(self, command): 24 | return match(f"^{NormalMode.COUNT}$", command) 25 | 26 | def is_find(self, command): 27 | return match(f"^({NormalMode.COUNT})?[fFtT]$", command) 28 | 29 | def is_char(self, command): 30 | return match("^[rq@'\"]$", command) 31 | 32 | def is_operator(self, command): 33 | return match(f"^[dcy<>=]({NormalMode.COUNT})?([iafFtT])?$", command) 34 | 35 | def is_command(self, command): 36 | return match("^:.*[^\u000D]$|^:$", command) 37 | 38 | def is_pending(self, command): 39 | if any([self.is_count(command), 40 | self.is_find(command), 41 | self.is_char(command), 42 | self.is_operator(command), 43 | self.is_command(command) 44 | ]): 45 | return True 46 | 47 | 48 | class LostMode(NormalMode): 49 | NAME = "GAME OVER" 50 | 51 | 52 | class InsertMode(VimMode): 53 | NAME = "-- INSERT --" 54 | 55 | def draw(self, items): 56 | for text, x, y in items: 57 | pygame.draw.rect(SCREEN, CURSOR_COLOR, (x, y, CHAR_WIDTH // 4, CHAR_HEIGHT)) 58 | 59 | 60 | class VisualMode(VimMode): 61 | NAME = "VISUAL" 62 | 63 | def is_operator(self, command): 64 | return match("^[ia]$", command) 65 | 66 | def is_pending(self, command): 67 | if self.is_operator(command): 68 | return True 69 | -------------------------------------------------------------------------------- /src/core/games.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from .constants import * 4 | from .modes import * 5 | 6 | 7 | class Game: 8 | def __init__(self): 9 | self.running = True 10 | self.spawner = None 11 | self.multiplier = 1 12 | self.restart() 13 | 14 | def restart(self): 15 | self.lost = False 16 | self.puzzles = [] 17 | self.command = "" 18 | self.last = "" 19 | self.credit = 25 20 | self.debit = 0 21 | 22 | def draw_command_line(self, text): 23 | # Line 24 | pygame.draw.rect(SCREEN, CURSOR_COLOR, (0, HEIGHT - CHAR_HEIGHT - BOTTOM, WIDTH, BOTTOM)) 25 | 26 | # Status 27 | draw_text(text, 0, HEIGHT-CHAR_HEIGHT, TEXT_COLOR) 28 | 29 | # Last Command 30 | last = self.command if self.command else self.last 31 | draw_text(last, WIDTH // 2, HEIGHT - CHAR_HEIGHT, TEXT_COLOR) 32 | 33 | # Cursor Position Vector 34 | row, col = NVIM.funcs.getpos(".")[1:3] 35 | draw_text(f"{row}:{col}", WIDTH - 100, HEIGHT-CHAR_HEIGHT, TEXT_COLOR) 36 | 37 | def draw_top(self): 38 | draw_text(f"Score: {self.credit}", WIDTH - CHAR_WIDTH * 11, 0, WHITE) 39 | draw_text(f"Keys Remaining: {self.credit - self.debit}", WIDTH - CHAR_WIDTH * 20, CHAR_HEIGHT, WHITE) 40 | 41 | def draw(self): 42 | SCREEN.fill(BACKGROUND_COLOR) 43 | self.draw_top() 44 | self.draw_command_line(self.mode.NAME) 45 | for puzzle in self.puzzles: 46 | puzzle.draw() 47 | 48 | pygame.display.flip() 49 | 50 | def test(self): 51 | for puzzle in self.puzzles: 52 | puzzle.test() 53 | 54 | def handle_events(self): 55 | for event in pygame.event.get(): 56 | if event.type == pygame.QUIT: 57 | self.running = False 58 | return 59 | 60 | if event.type == pygame.KEYDOWN: 61 | self.command += event.unicode 62 | print(self.mode.NAME, event.unicode) 63 | if self.mode.is_pending(self.command): 64 | print("defering", self.command) 65 | continue 66 | 67 | self.flush() 68 | 69 | @property 70 | def mode(self): 71 | if self.lost: 72 | return LostMode(self) 73 | 74 | mode = NVIM.eval("mode()") 75 | return { 76 | "n": NormalMode, 77 | "i": InsertMode, 78 | "v": VisualMode, 79 | }.get(mode, NormalMode)(self) 80 | 81 | @property 82 | def puzzle(self): 83 | return self.puzzles[0] 84 | 85 | def run(self): 86 | while self.running: 87 | self.handle_events() 88 | self.test() 89 | self.draw() 90 | if not self.lost: 91 | self.spawn() 92 | self.check_score() 93 | CLOCK.tick(FPS) 94 | 95 | self.quit() 96 | 97 | def spawn(self): 98 | self.spawner.spawn() 99 | 100 | def check_score(self): 101 | if self.credit <= self.debit: 102 | self.lost = True 103 | self.puzzles = [] 104 | 105 | def flush(self): 106 | if self.command == ":q\r": 107 | self.quit() 108 | return 109 | 110 | if self.command == ":n\r": 111 | self.restart() 112 | return 113 | 114 | NVIM.input(self.command) 115 | self.last = self.command 116 | if not self.lost: 117 | self.debit += len(self.command) 118 | self.command = "" 119 | 120 | def quit(self): 121 | self.running = False 122 | pygame.quit() 123 | sys.exit() 124 | -------------------------------------------------------------------------------- /src/core/puzzles.py: -------------------------------------------------------------------------------- 1 | from .constants import * 2 | from .modes import NormalMode 3 | from .buffers import Buffer 4 | from .diffs import get_diff 5 | 6 | 7 | class LineDrawer: 8 | COLORS = { 9 | "insert": GREEN, 10 | "equal": WHITE, 11 | "delete": RED 12 | } 13 | 14 | def __init__(self, text, target): 15 | self.target = target 16 | self.text = text 17 | self.words = get_diff(self.text, self.target) 18 | 19 | def draw(self, x, y): 20 | col = 0 21 | for op, string in self.words: 22 | dy = 0 23 | if op == "insert": 24 | dy = -CHAR_HEIGHT 25 | 26 | text_surface = FONT.render(string, True, LineDrawer.COLORS[op]) 27 | text_rect = text_surface.get_rect(topleft=(x + CHAR_WIDTH * col, y + dy)) 28 | SCREEN.blit(text_surface, text_rect) 29 | 30 | if op != "insert": 31 | col += len(string) 32 | 33 | 34 | class Puzzle: 35 | def __init__(self, game, lines, par, x=20, y=20): 36 | self.game = game 37 | self.buffer = Buffer(lines) 38 | self.par = par 39 | self.x, self.y = x, y 40 | 41 | @property 42 | def lines(self): 43 | return self.buffer.lines 44 | 45 | @property 46 | def height(self): 47 | return CHAR_HEIGHT * self.buffer.rows 48 | 49 | @property 50 | def width(self): 51 | return CHAR_WIDTH * self.buffer.cols 52 | 53 | def get_rect(self): 54 | return self.x, self.y, self.width, self.height 55 | 56 | def get_coord(self, row, col): 57 | return ( 58 | self.get_x_coord(col), 59 | self.get_y_coord(row) 60 | ) 61 | 62 | def get_x_coord(self, col): 63 | return self.x + CHAR_WIDTH * col 64 | 65 | def get_y_coord(self, row): 66 | return self.y + CHAR_HEIGHT * row 67 | 68 | def draw_background(self): 69 | # Draw background 70 | pygame.draw.rect(SCREEN, WORD_BACKGROUND_COLOR, self.get_rect()) 71 | 72 | def draw_text(self): 73 | # Draw text 74 | for n, line in enumerate(self.lines): 75 | draw_text(line, self.x, self.get_y_coord(n), WHITE) 76 | 77 | def get_char(self, row, col): 78 | line = self.buffer.lines[row] 79 | if col >= len(line): 80 | return "" 81 | 82 | return line[col] 83 | 84 | def draw_cursor(self): 85 | buff_no, row, col, _ = NVIM.funcs.getpos(".") 86 | if NVIM.current.buffer == self.buffer.buffer: 87 | row -= 1 88 | col -= 1 89 | char = self.get_char(row, col) 90 | x, y = self.get_coord(row, col) 91 | self.game.mode.draw([(char, x, y)]) 92 | 93 | def draw_extra(self): 94 | pass 95 | 96 | def draw(self): 97 | self.draw_background() 98 | self.draw_text() 99 | self.draw_cursor() 100 | self.draw_extra() 101 | self.update() 102 | 103 | def update(self): 104 | pass 105 | 106 | def is_solved(self): 107 | pass 108 | 109 | def is_failed(self): 110 | pass 111 | 112 | def test(self): 113 | if self.is_solved() and isinstance(self.game.mode, NormalMode): 114 | self.game.credit += self.game.multiplier * self.par 115 | self.delete() 116 | elif self.is_failed(): 117 | self.delete() 118 | 119 | def delete(self): 120 | self.game.puzzles.remove(self) 121 | 122 | 123 | class FallingPuzzle(Puzzle): 124 | def __init__(self, game, lines, par, x, y, speed=50): 125 | super().__init__(game, lines, par, x, y) 126 | self.speed = speed 127 | 128 | def update(self): 129 | self.y += self.speed / FPS 130 | 131 | def is_failed(self): 132 | return self.y + self.height + BOTTOM > HEIGHT - CHAR_HEIGHT 133 | 134 | 135 | class EditPuzzle(FallingPuzzle): 136 | def __init__(self, game, lines, targets, x=20, y=20, par=6, speed=10): 137 | super().__init__(game, lines, par, x, y, speed) 138 | self.targets = targets 139 | 140 | @property 141 | def height(self): 142 | return 2 * CHAR_HEIGHT * max(self.buffer.rows, len(self.targets)) 143 | 144 | @property 145 | def width(self): 146 | return CHAR_WIDTH * max(self.buffer.cols, max([len(target) for target in self.targets])) 147 | 148 | def get_y_coord(self, row): 149 | return self.y + CHAR_HEIGHT * (2 * row + 1) 150 | 151 | def draw_text(self): 152 | # Draw text 153 | for n, (line, target) in enumerate(zip(self.lines, self.targets)): 154 | line = LineDrawer(line, target) 155 | line.draw(self.x, self.get_y_coord(n)) 156 | 157 | def is_solved(self): 158 | return all([line == target for line, target in zip(self.lines, self.targets)]) 159 | 160 | 161 | class MovementPuzzle(FallingPuzzle): 162 | def __init__(self, game, x, y, speed, n_rows, n_cols, rows, cols): 163 | lines = [" " * n_cols] * n_rows 164 | self.rows = rows 165 | self.cols = cols 166 | par = 0 167 | rowp = colp = 0 168 | for row, col in zip(self.rows, self.cols): 169 | par += abs(row-rowp) + abs(col-colp) 170 | rowp, colp = row, col 171 | 172 | 173 | super().__init__(game, lines, par, x, y, speed) 174 | 175 | def is_solved(self): 176 | buff_no, row, col, _ = NVIM.funcs.getpos(".") 177 | if self.rows[-1] == row - 1 and self.cols[-1] == col - 1: 178 | self.rows.pop() 179 | self.cols.pop() 180 | return len(self.rows) == 0 181 | 182 | def draw_extra(self): 183 | char = self.get_char(self.rows[-1], self.cols[-1]) 184 | x, y = self.get_coord(self.rows[-1], self.cols[-1]) 185 | pygame.draw.rect(SCREEN, ORANGE, (x, y, CHAR_WIDTH, CHAR_HEIGHT)) 186 | draw_text(char, x, y, CURSOR_TEXT_COLOR) 187 | -------------------------------------------------------------------------------- /src/core/spawners/spawners.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import inspect 3 | from random import choice 4 | 5 | from ..puzzles import EditPuzzle, MovementPuzzle 6 | 7 | 8 | def get_spawners(): 9 | self = sys.modules[__name__] 10 | return [obj for name, obj in inspect.getmembers(self, inspect.isclass) if 11 | obj.__module__ == __name__ and name not in {"RandomSpawner", "Spawner", "EditSpawner"}] 12 | 13 | 14 | class RandomSpawner: 15 | def __init__(self, game, spawners=None): 16 | self.game = game 17 | self.spawners = spawners if spawners else get_spawners() 18 | print(self.spawners) 19 | 20 | def spawn(self): 21 | if len(self.game.puzzles) < 1: 22 | cls = choice(self.spawners) 23 | return cls(self.game) 24 | 25 | 26 | def get_random_letter(): 27 | return choice("abcdefghijklmnopqrstuvwxyz") 28 | 29 | 30 | class EditSpawner: 31 | def __init__(self, game, texts, targets, par, speed=50): 32 | puzzle = EditPuzzle(game, texts, targets, par=par, speed=speed) 33 | game.puzzles.append(puzzle) 34 | 35 | 36 | class StartReplaceSpawner(EditSpawner): 37 | def __init__(self, game): 38 | texts = ["The dear fegan to park feer."] 39 | targets = ["The bear began to bark beer."] 40 | super().__init__(game, texts=texts, targets=targets, par=10) 41 | 42 | 43 | class EndReplaceSpawner(EditSpawner): 44 | def __init__(self, game): 45 | texts = ["The bean used tan to ban the can."] 46 | targets = ["The bear used tar to bar the car."] 47 | super().__init__(game, texts=texts, targets=targets, par=13) 48 | 49 | 50 | class FindReplaceSpawner(EditSpawner): 51 | def __init__(self, game): 52 | texts = ["The feir of mixing out."] 53 | targets = ["The fear of maxing out."] 54 | super().__init__(game, texts=texts, targets=targets, par=6) 55 | 56 | 57 | class StartSpawner(EditSpawner): 58 | def __init__(self, game): 59 | texts = [ 60 | "This is the first item.", 61 | "This is the second item.", 62 | "This is the third item." 63 | ] 64 | prefix = choice(["-", "+", "[ ]", "[x]"]) + " " 65 | targets = [prefix + text for text in texts] 66 | super().__init__(game, texts=texts, targets=targets, par=8) 67 | 68 | 69 | class EndSpawner(EditSpawner): 70 | def __init__(self, game): 71 | targets = [ 72 | "(1) Open the peanut butter.", 73 | "(2) Put a knife in the peanut butter.", 74 | "(3) Spread the peanut butter on bread." 75 | ] 76 | texts = [ 77 | "(1) Open the peanut butter", 78 | "(2) Put a knife in the peanut butter", 79 | "(3) Spread the peanut butter on bread" 80 | ] 81 | 82 | super().__init__(game, texts=texts, targets=targets, par=7) 83 | 84 | 85 | class FindDeleteCharSpawner(EditSpawner): 86 | TEXTS = [ 87 | "The quick brown fox jumped.", 88 | "This is a (sample) word.", 89 | "Delete all the random characters!" 90 | ] 91 | 92 | def __init__(self, game): 93 | target = choice(self.TEXTS) 94 | inds = list(range(len(target))) 95 | text = target 96 | letter = "x" 97 | count = choice([1, 2, 3]) 98 | for _ in range(count): 99 | ind = choice(inds) 100 | inds.remove(ind) 101 | text = text[:ind] + letter + text[ind:] 102 | 103 | super().__init__(game, [text], [target], par=3 + 2 * (count - 1)) 104 | 105 | 106 | class FindDeleteToSpawner(EditSpawner): 107 | def __init__(self, game): 108 | text = "This sentence is short, and this part is not needed." 109 | target = "This sentence is short." 110 | super().__init__(game, [text], [target], par=5) 111 | 112 | 113 | class StartWordInsertSpawner(EditSpawner): 114 | def __init__(self, game): 115 | text = "The sults verted to the original." 116 | target = "The results reverted to the original." 117 | super().__init__(game, [text], [target], par=7) 118 | 119 | 120 | class EndWordInsertSpawner(EditSpawner): 121 | def __init__(self, game): 122 | text = "The test are indicat." 123 | target = "The testing are indicating." 124 | super().__init__(game, [text], [target], par=10) 125 | 126 | 127 | class StartWordDeleteSpawner(EditSpawner): 128 | def __init__(self, game): 129 | text = "I reinstated my everlong photosynthesis." 130 | target = "I instated my long synthesis." 131 | super().__init__(game, [text], [target], par=10) 132 | 133 | 134 | class EndWordDeleteSpawner(EditSpawner): 135 | def __init__(self, game): 136 | text = "Starting going for the lasting time." 137 | target = "Start go for the last time." 138 | super().__init__(game, [text], [target], par=8) 139 | 140 | 141 | class DeleteWordSpawner(EditSpawner): 142 | def __init__(self, game): 143 | target = "Please delete all the repeated words." 144 | text = "Please delete delete all the the repeated words words." 145 | super().__init__(game, [text], [target], par=9) 146 | 147 | 148 | class SubsSpawner(EditSpawner): 149 | def __init__(self, game): 150 | text = 'var foo = "method("+arg1+","+arg2+");' 151 | target = 'var foo = "method(" + arg1 + "," + arg2 + ");' 152 | super().__init__(game, [text], [target], par=13) 153 | 154 | 155 | class ChangeWordSpawner(EditSpawner): 156 | def __init__(self, game): 157 | target = "The more wine you have, the better your wine tastes." 158 | text = "The more food you have, the better your food tastes." 159 | super().__init__(game, [text], [target], par=10) 160 | 161 | 162 | class DeleteAroundWordSpawner(EditSpawner): 163 | def __init__(self, game): 164 | targets = [ 165 | "The first sign of you", 166 | "Is impeccable timing", 167 | "A simple poem" 168 | ] 169 | texts = [ 170 | "The first sign sign of you", 171 | "Is impeccable impeccable timing", 172 | "A simple simple poem" 173 | ] 174 | super().__init__(game, texts, targets, par=8) 175 | 176 | 177 | class ChangeToEndSpawner(EditSpawner): 178 | def __init__(self, game): 179 | texts = ["You know I love you, but you always do that"] 180 | targets = ["You know I love you."] 181 | super().__init__(game, texts, targets, par=5) 182 | 183 | 184 | class DeleteToEndSpawner(EditSpawner): 185 | def __init__(self, game): 186 | texts = ["I cant believe you did that, you always do that!"] 187 | targets = ["I cant believe you did that"] 188 | super().__init__(game, texts, targets, par=3) 189 | 190 | 191 | class MovementSpawner: 192 | def __init__(self, game): 193 | N = 1 194 | n_rows = 5 195 | n_cols = 11 196 | rows = [choice(range(n_rows)) for _ in range(N)] 197 | cols = [choice(range(n_cols)) for _ in range(N)] 198 | puzzle = MovementPuzzle(game, x=20, y=20, speed=50, n_rows=5, n_cols=11, rows=rows, cols=cols) 199 | game.puzzles.append(puzzle) --------------------------------------------------------------------------------