[
  {
    "path": ".gitignore",
    "content": ".idea\n.DS_Store\n# Binaries for programs and plugins\n*.exe\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736\n.glide/\nlogfile.txt\n.swp\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Ozan Kaşıkçı\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# VimMan\n\nLearn how to use Vim in its natural environment, the Terminal!\n## About\n\nVimMan is terminal program that's a semi editor and a semi game. The purpose of VimMan is to teach people how to use vim and have fun at the same time! \n\n## Installation\n\n`git clone https://github.com/ozankasikci/vim-man && cd vim-man`\n\n`go run cmd/console/vimman.go`\n\nto start from a specific level;\n\n`LEVEL=2 go run cmd/console/vimman.go`\n\n## Demo\n### Level - 1 - Basic movement in Normal Mode\n\n![](https://raw.githubusercontent.com/ozankasikci/ozankasikci.github.io/master/gifs/fantasia-level-1.gif)\n\n### Level - 2 - How to exit Vim\n\n![](https://raw.githubusercontent.com/ozankasikci/ozankasikci.github.io/master/gifs/fantasia-level-2.gif)\n\n### Level - 3 - Basic text editing\n\n![](https://raw.githubusercontent.com/ozankasikci/ozankasikci.github.io/master/gifs/fantasia-level-3.gif)\n\n### Level - 4 - Vimberman!\n\n![](https://raw.githubusercontent.com/ozankasikci/ozankasikci.github.io/master/gifs/fantasia-level-4.gif)\n\n## TODO\n* Add missing levels\n  - [ ] File save\n  - [ ] Deletion commands level (`dw`, `d$` vs)\n  - [ ] Operators and motions\n  - [ ] Using count\n  - [ ] Operating on lines\n  - [ ] Undo & redo\n  - [ ] Put, replace, change operators\n  - [ ] Search & substitute\n  - [ ] Accessing Shell\n* Handle edge cases in some levels\n  - [ ] level 3 \n"
  },
  {
    "path": "canvas.go",
    "content": "package vimman\n\nimport \"os\"\n\n//import tb \"github.com/nsf/termbox-go\"\n\ntype Canvas [][]*TermBoxCell\n\nfunc NewCanvas(width, height int) Canvas {\n\tcanvas := make(Canvas, height)\n\tfor i := range canvas {\n\t\tcanvas[i] = make([]*TermBoxCell, width)\n\t}\n\treturn canvas\n}\n\nfunc (c Canvas) GetCellAt(x, y int) *TermBoxCell {\n\treturn c[y][x]\n}\n\nfunc (c Canvas) CheckCollision(x, y int) bool {\n\t// check if out of boundaries\n\tif x < 0 || y < 0 || y >= len(c) || x >= len(c[0]) {\n\t\treturn true\n\t}\n\n\tif c[y][x] == nil {\n\t\treturn true\n\t}\n\n\tif c[y][x].cellData.CollisionCallback != nil {\n\t\tc[y][x].cellData.CollisionCallback()\n\t}\n\n\tif os.Getenv(\"DEBUG\") == \"1\" {\n\t\treturn false\n\t}\n\n\treturn c[y][x].collidesPhysically\n}\n\nfunc (c *Canvas) OverWriteCanvasCell(x, y int, termboxCell *TermBoxCell) {\n\tif x >= 0 && x < len((*c)[0]) && y >= 0 && y < len((*c)) {\n\t\t// intentionally use x,y in reverse order\n\t\t(*c)[y][x] = termboxCell\n\t}\n}\n\nfunc (c *Canvas) SetCellAt(row, column int, cell *TermBoxCell) {\n\t(*c)[row][column] = cell\n}\n\nfunc (c *Canvas) IsInsideOfBoundaries(x, y int) bool {\n\treturn x >= 0 && x < len((*c)[0])-1 && y >= 0 && y < len(*c)-1\n}\n\nfunc (c *Canvas) IsInLastColumn(x int) bool {\n\treturn len((*c)[0])-1 == x\n}\n"
  },
  {
    "path": "canvas_test.go",
    "content": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n)\n\nfunc TestCanvasCheckCollision(t *testing.T) {\n\tx, y := 10, 10\n\tc := NewCanvas(x, y)\n\tc[1][1] = &TermBoxCell{&termbox.Cell{}, false, nil}\n\tc[0][0] = &TermBoxCell{&termbox.Cell{}, true, nil}\n\n\ttt := []struct {\n\t\tx      int\n\t\ty      int\n\t\texpect bool\n\t}{\n\t\t{-1, 0, true},\n\t\t{0, -1, true},\n\t\t{1, 1, false},\n\t\t{0, 0, true},\n\t\t{x, 0, true},\n\t\t{0, y, true},\n\t}\n\n\tfor _, value := range tt {\n\t\tres := c.CheckCollision(value.x, value.y)\n\t\tassert.Equal(t, value.expect, res)\n\t}\n\n\tc[2][2] = nil\n\tassert.True(t, c.CheckCollision(2, 2))\n}\n"
  },
  {
    "path": "cmd/console/vimman.go",
    "content": "package main\n\nimport (\n\t\"github.com/ozankasikci/vim-man\"\n\t\"os\"\n\t\"strconv\"\n)\n\nfunc main() {\n\tlevel := os.Getenv(\"LEVEL\")\n\tlevelInt, err := strconv.ParseInt(level, 10, 16)\n\tif err != nil {\n\t\tlevelInt = 1\n\t}\n\n\tvimman.Init(int(levelInt))\n}\n"
  },
  {
    "path": "docs/LEVEL_PLANNING.md",
    "content": "# Vim-Man Level Planning\n\n## Level 1: Basic Movement\n\n*   **Vim Concepts:** `h` (left), `j` (down), `k` (up), `l` (right).\n*   **Gameplay:** A simple maze. Player learns to navigate Vim-Man using the basic movement keys to reach an exit tile. Introduce the concept of \"Vim\" as the source of power/commands.\n*   **Visuals:** Clean, straightforward. Maybe a terminal-like aesthetic.\n*   **Objective:** Reach the exit.\n\n## Level 2: Exiting Vim (The First Challenge)\n\n*   **Vim Concepts:** `:` (command mode), `q` (quit), `!` (force - for `q!`).\n*   **Gameplay:** Player reaches what seems like an exit, but it's blocked. A hint appears: \"Type :q to exit\". If the player has \"unsaved changes\" (e.g., collected an item they weren't supposed to, or stepped on a 'dirty' tile), `:q` fails. They then learn `:q!` to force quit (exit the level).\n*   **Visuals:** The exit could be a flashing cursor or a representation of a closing window. \"Dirty\" tiles could have a distinct visual.\n*   **Objective:** Successfully exit the level using the correct quit command.\n\n## Level 3: Basic Text Editing (Deletion)\n\n*   **Vim Concepts:** `x` (delete character under cursor), `dw` (delete word - maybe simplified to just deleting a 'word' entity).\n*   **Gameplay:** The path is blocked by \"error characters\" (like `x` tiles) or \"bug words\" (small enemy-like entities). Player must use `x` to delete single error characters or `dw` (or a simplified version) to remove \"bug words\" to clear the path.\n*   **Visuals:** \"Error characters\" could be red `x`'s. \"Bug words\" could be small, distinct sprites.\n*   **Objective:** Clear the path and reach the exit.\n\n## Level 4: The Bomberman (Introduction to Insert Mode & Esc)\n\n*   **Vim Concepts:** `i` (insert mode), `Esc` (return to normal mode), placing \"bombs\" (characters) in insert mode.\n*   **Gameplay:** Inspired by Bomberman. Player can enter insert mode with `i`. While in insert mode, pressing a movement key (or a specific \"bomb\" key) places a \"character bomb\" that explodes after a short delay, clearing destructible blocks. Player must use `Esc` to return to normal mode to move safely.\n*   **Visuals:** Destructible blocks, bomb sprites, explosion effects.\n*   **Objective:** Clear a path through destructible blocks and reach the exit.\n\n## Level 5: The Word Jumper's Gauntlet\n\n*   **Vim Concepts:** `w` (next word), `b` (previous word), `e` (end of word), `ge` (end of previous word).\n*   **Gameplay:** The level is a series of platforms (words) separated by gaps. Player must use `w`, `b`, `e`, `ge` to jump precisely between platforms. Some platforms might be crumbling, requiring quick successive jumps. Enemies could patrol on longer \"sentence\" platforms.\n*   **Visuals:** Platforms clearly look like \"words\". Gaps are obvious. Crumbling platforms could animate.\n*   **Objective:** Navigate the platforms and reach the exit.\n\n## Level 6: The Repetition Realm (Numeric Precision)\n\n*   **Vim Concepts:** Using numbers with motion/commands (e.g., `3w`, `5j`, `2dd`).\n*   **Gameplay:** Paths are blocked by gates requiring a specific number of actions. For example, a gate \"3\" high requires `3j` to pass if it's a vertical jump, or defeating an enemy might take `2x` (two delete actions). Collectibles could be arranged in patterns that are easiest to get with numbered movements.\n*   **Visuals:** Gates could display the number required. Collectibles in clear numerical patterns.\n*   **Objective:** Use numbered commands to overcome obstacles and reach the exit.\n\n## Level 7: The Search & Find Expedition\n\n*   **Vim Concepts:** `/` (search forward), `?` (search backward), `n` (next occurrence), `N` (previous occurrence).\n*   **Gameplay:** Key items or \"rescue targets\" (e.g., specific characters) are hidden across a maze-like map. Using `/` or `?` followed by the target character would highlight the path or the item. Enemies might change characters, forcing the player to re-search.\n*   **Visuals:** Maze environment. Searched items/paths could glow or have a special indicator.\n*   **Objective:** Find the target(s) using search commands and reach the exit.\n\n## Level 8: The Copy-Paste Assembly Line\n\n*   **Vim Concepts:** `y` (yank/copy), `p` (paste after), `P` (paste before), Visual mode (`v` character-wise, `V` line-wise) for selecting text.\n*   **Gameplay:** The player needs to construct a specific \"word\" or \"code snippet\" by yanking parts from different areas of the level and pasting them into a designated \"build zone.\" Visual mode could be used to select multi-character \"components.\"\n*   **Visuals:** Distinct \"source\" areas for yanking. A clear \"build zone\" where pasting occurs. Visual feedback for selection in Visual mode.\n*   **Objective:** Assemble the target word/snippet and complete the level. "
  },
  {
    "path": "entity.go",
    "content": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"time\"\n)\n\ntype Direction int\n\nconst (\n\thorizontal Direction = iota\n\tvertical\n)\n\ntype Entity struct {\n\tStage        *Stage\n\tPosition     Point\n\tWidth        int\n\tHeight       int\n\tRune         rune\n\tCell         *TermBoxCell\n\tCells        []*TermBoxCell\n\tDrawPriority int\n\tTags         []Tag\n\tInitCallback func()\n}\n\ntype EntityOptions struct {\n\tDrawPriority int\n\tTags         []Tag\n\tInitCallback func()\n}\n\ntype Tag struct {\n\tName string\n}\n\nfunc NewEntity(s *Stage, x, y, w, h int, r rune, fg termbox.Attribute, bg termbox.Attribute, cells []*TermBoxCell, collidesPhysically bool, options EntityOptions) *Entity {\n\tdrawPriority, tags, initCallback := options.DrawPriority, options.Tags, options.InitCallback\n\tp := Point{x, y}\n\tcell := &TermBoxCell{&termbox.Cell{r, fg, bg}, collidesPhysically, TileMapCellData{}}\n\treturn &Entity{s, p, w, h, r, cell, cells, drawPriority, tags, initCallback}\n}\n\nfunc (e *Entity) SetStage(s *Stage) {\n\te.Stage = s\n}\n\nfunc (e *Entity) GetStage() *Stage {\n\treturn e.Stage\n}\n\nfunc (e *Entity) SetCells(s *Stage) {\n\tnewPositionY := e.Position.y\n\n\tfor i := 0; i < e.Height; i++ {\n\t\tnewPositionX := e.Position.x\n\t\tif i != 0 {\n\t\t\tnewPositionY += 1\n\t\t}\n\n\t\tfor j := 0; j < e.Width; j++ {\n\t\t\tif j != 0 {\n\t\t\t\tnewPositionX += 1\n\t\t\t}\n\n\t\t\tif e.Cells != nil {\n\t\t\t\tindex := j\n\n\t\t\t\ttileMapCell := e.Cells[index]\n\t\t\t\tif len(e.Cells) > index {\n\t\t\t\t\ts.Canvas.OverWriteCanvasCell(newPositionX, newPositionY, tileMapCell)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttileMapCell := e.Cell\n\t\t\t\ts.Canvas.OverWriteCanvasCell(newPositionX, newPositionY, tileMapCell)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (e *Entity) GetCells() []*TermBoxCell {\n\treturn e.Cells\n}\n\nfunc (e *Entity) Update(s *Stage, event termbox.Event, time time.Duration) {\n}\n\nfunc (e *Entity) SetPosition(x, y int) {\n\te.SetPositionX(x)\n\te.SetPositionY(y)\n}\n\nfunc (e *Entity) SetPositionX(x int) {\n\te.Position.x = x\n}\n\nfunc (e *Entity) SetPositionY(y int) {\n\te.Position.y = y\n}\n\nfunc (e *Entity) GetPositionX() int {\n\treturn e.Position.x\n}\n\nfunc (e *Entity) GetPositionY() int {\n\treturn e.Position.y\n}\n\nfunc (e *Entity) GetPosition() (int, int) {\n\treturn e.Position.x, e.Position.y\n}\n\nfunc (e *Entity) GetScreenOffset() (int, int) {\n\tscreenWidth, screenHeight := e.Stage.Game.getScreenSize()\n\treturn (screenWidth - e.Width) / 2, (screenHeight - e.Height) / 2\n}\n\nfunc (e *Entity) Destroy() {\n}\n\nfunc (e *Entity) GetDrawPriority() int {\n\treturn e.DrawPriority\n}\n\nfunc (e *Entity) GetTags() []Tag {\n\treturn e.Tags\n}\n\nfunc (e *Entity) IsInsideOfCanvasBoundaries() bool {\n\treturn e.GetStage().Canvas.IsInsideOfBoundaries(e.GetPositionX(), e.GetPositionY())\n}\n"
  },
  {
    "path": "game.go",
    "content": "package vimman\n\nimport (\n\t\"time\"\n\n\t\"github.com/nsf/termbox-go\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\tbgColor = termbox.ColorBlack\n\tfgColor = termbox.ColorWhite\n)\n\ntype Point struct {\n\tx int\n\ty int\n}\n\ntype Game struct {\n\tStage        *Stage\n\tscreenSizeX  int\n\tscreenSizeY  int\n\tVimManEvents chan VimManEvent\n}\n\ntype GameOptions struct {\n\tfps          float64\n\tinitialLevel int\n\tbgCell       *termbox.Cell\n\tVimManEvents chan VimManEvent\n}\n\ntype VimManEvent struct {\n\tContent string\n}\n\nfunc NewGame(opts GameOptions) *Game {\n\tbgCell := &termbox.Cell{'░', fgColor, bgColor}\n\tgame := &Game{nil, 0, 0, opts.VimManEvents}\n\tstage := NewStage(game, opts.initialLevel, opts.fps, bgCell)\n\tgame.Stage = stage\n\treturn game\n}\n\ntype Renderer interface {\n\tUpdate(*Stage, termbox.Event, time.Duration)\n\tDestroy()\n\tSetCells(*Stage)\n\tGetCells() []*TermBoxCell\n\tGetPosition() (int, int)\n\tGetPositionX() int\n\tGetPositionY() int\n\tSetPositionX(int)\n\tGetScreenOffset() (int, int)\n\tGetDrawPriority() int\n\tGetTags() []Tag\n\tShouldCenterHorizontally() bool\n}\n\n// main game loop\n// handles events, updates and renders stage and entities\nfunc gameLoop(termboxEvents chan termbox.Event, vimManEvents chan VimManEvent, game *Game) {\n\ttermbox.Clear(fgColor, bgColor)\n\tgame.setScreenSize(termbox.Size())\n\tstage := game.Stage\n\tstage.Init()\n\tstage.Render()\n\tlastUpdateTime := time.Now()\n\n\tfor {\n\t\ttermbox.Clear(fgColor, bgColor)\n\t\tupdate := time.Now()\n\n\t\tselect {\n\t\tcase event := <-termboxEvents:\n\t\t\tswitch {\n\t\t\tcase event.Key == termbox.KeyCtrlC:\n\t\t\t\t// exit on ctrc + c\n\t\t\t\treturn\n\t\t\tcase event.Type == termbox.EventResize:\n\t\t\t\tgame.setScreenSize(termbox.Size())\n\t\t\tdefault:\n\t\t\t\tstage.update(event, update.Sub(lastUpdateTime))\n\t\t\t}\n\t\tdefault:\n\t\t\tstage.update(termbox.Event{}, update.Sub(lastUpdateTime))\n\t\t}\n\n\t\t// handle vim-man events here\n\t\tselect {\n\t\tcase event := <-vimManEvents:\n\t\t\tswitch {\n\t\t\tcase event.Content == \"exit\":\n\t\t\t\treturn\n\t\t\t}\n\t\tdefault:\n\n\t\t}\n\n\t\tlastUpdateTime = time.Now()\n\n\t\tstage.Render()\n\t\ttime.Sleep(time.Duration((update.Sub(time.Now()).Seconds()*1000.0)+1000.0/stage.Fps) * time.Millisecond)\n\t}\n}\n\nfunc termboxEventLoop(e chan termbox.Event) {\n\tfor {\n\t\te <- termbox.PollEvent()\n\t}\n}\n\nfunc exit(events chan termbox.Event) {\n\tclose(events)\n\ttermbox.Close()\n}\n\nfunc Init(level int) {\n\tif err := termbox.Init(); err != nil {\n\t\tpanic(errors.Wrap(err, \"failed to init termbox\"))\n\t}\n\n\tif level == 0 {\n\t\tlevel = 1\n\t}\n\n\ttermbox.SetOutputMode(termbox.Output256)\n\ttermbox.Clear(termbox.ColorDefault, bgColor)\n\n\ttermboxEvents := make(chan termbox.Event)\n\tgo termboxEventLoop(termboxEvents)\n\n\tvimManEvents := make(chan VimManEvent)\n\n\tgame := NewGame(GameOptions{\n\t\tfps:          50,\n\t\tinitialLevel: level,\n\t\tVimManEvents: vimManEvents,\n\t})\n\n\t// main game loop, this is blocking\n\tgameLoop(termboxEvents, vimManEvents, game)\n\n\t// dump logs after the gameLoop stops\n\tif len(lg.logs) > 0 {\n\t\tlg.DumpLogs()\n\t\ttime.Sleep(2 * time.Second)\n\t}\n\n\texit(termboxEvents)\n}\n\nfunc (g *Game) setScreenSize(x, y int) {\n\tif x > 0 {\n\t\tg.screenSizeX = x\n\t}\n\n\tif y > 0 {\n\t\tg.screenSizeY = y\n\t}\n}\n\nfunc (g *Game) getScreenSize() (int, int) {\n\treturn g.getScreenSizeX(), g.getScreenSizeY()\n}\n\nfunc (g *Game) getScreenSizeX() int {\n\treturn g.screenSizeX\n}\n\nfunc (g *Game) getScreenSizeY() int {\n\treturn g.screenSizeY\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/ozankasikci/vim-man\n\ngo 1.13\n\nrequire (\n\tgithub.com/mattn/go-runewidth v0.0.8 // indirect\n\tgithub.com/nsf/termbox-go v0.0.0-20191229070316-58d4fcbce2a7\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/stretchr/testify v1.4.0\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=\ngithub.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/nsf/termbox-go v0.0.0-20191229070316-58d4fcbce2a7 h1:OkWEy7aQeQTbgdrcGi9bifx+Y6bMM7ae7y42hDFaBvA=\ngithub.com/nsf/termbox-go v0.0.0-20191229070316-58d4fcbce2a7/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\n"
  },
  {
    "path": "level.go",
    "content": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"reflect\"\n\t\"time\"\n)\n\ntype VimMode int\n\nconst (\n\tnormalMode VimMode = iota\n\tinsertMode\n\tcolonMode\n)\n\nfunc (m VimMode) String() string {\n\treturn [...]string{\"NORMAL\", \"INSERT\", \"NORMAL\"}[m]\n}\n\nconst (\n\tlevelTitleCoordX       int               = 0\n\tlevelTitleCoordY       int               = 1\n\tlevelTitleFg           termbox.Attribute = termbox.ColorGreen\n\tlevelTitleBg           termbox.Attribute = termbox.ColorBlack\n\tlevelExplanationCoordX int               = 0\n\tlevelExplanationCoordY int               = 2\n\tlevelHintCoordX        int               = 0\n\tlevelHintCoordY        int               = 3\n\ttypedCharacterFg       termbox.Attribute = termbox.ColorWhite\n\ttypedCharacterBg       termbox.Attribute = termbox.ColorBlack\n)\n\ntype Level struct {\n\tGame                 *Game\n\tVimMode              VimMode\n\tTileMapString        string\n\tTileMap              [][]*TermBoxCell\n\tTileData             TileMapCellDataMap\n\tEntities             []Renderer\n\tInputRunes           []rune\n\tBlockedKeys          []termbox.Key\n\tInputBlocked         bool\n\tTextShiftingDisabled bool\n\tBgCell               *termbox.Cell\n\tWidth                int\n\tHeight               int\n\tInit                 func()\n\tColonLineCallbacks   map[string]func(game *Game)\n}\n\nfunc (l *Level) Update(s *Stage, t time.Duration) {\n\n}\n\nfunc (l *Level) SetCells(s *Stage) {\n\n}\n\nfunc (l *Level) GetSize() (int, int) {\n\tindex, length := 0, 0\n\n\tfor i, line := range l.TileMap {\n\t\tif len(line) > length {\n\t\t\tindex, length = i, len(line)\n\t\t}\n\t}\n\n\treturn len(l.TileMap[index]), len(l.TileMap)\n}\n\nfunc (l *Level) GetScreenOffset() (int, int) {\n\toffsetX, offsetY := 0, 0\n\tscreenWidth, screenHeight := l.Game.getScreenSize()\n\tlevelWidth, levelHeight := l.GetSize()\n\n\tif screenWidth > levelWidth {\n\t\toffsetX = (screenWidth - levelWidth) / 2\n\t}\n\n\tif screenHeight > levelHeight {\n\t\toffsetY = (screenHeight - levelHeight) / 2\n\t}\n\n\treturn offsetX, offsetY\n}\n\nfunc (l *Level) LoadTileMapCells(parsedRunes [][]rune) [][]*TermBoxCell {\n\tvar cells [][]*TermBoxCell\n\n\tfor i, line := range parsedRunes {\n\t\trowCells := make([]*TermBoxCell, len(line))\n\t\tvar data TileMapCellData\n\n\t\tfor j, char := range line {\n\t\t\tif _, ok := l.TileData[char]; !ok {\n\t\t\t\tif _, ok := CommonTileMapCellData[char]; !ok {\n\t\t\t\t\tdata = NewTileMapCell(char, func() {}, i)\n\t\t\t\t} else {\n\t\t\t\t\tdata = CommonTileMapCellData[char]\n\t\t\t\t\tdata.LineNumber = i\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata = l.TileData[char]\n\t\t\t}\n\n\t\t\tif reflect.DeepEqual(data, TileMapCellData{}) {\n\t\t\t\tdata = CommonTileMapCellData[char]\n\t\t\t}\n\n\t\t\tcell := &TermBoxCell{\n\t\t\t\t&termbox.Cell{data.Ch, data.FgColor, data.BgColor},\n\t\t\t\tdata.CollidesPhysically,\n\t\t\t\tdata,\n\t\t\t}\n\t\t\trowCells[j] = cell\n\t\t}\n\n\t\tcells = append(cells, rowCells)\n\t}\n\n\tl.TileMap = cells\n\treturn l.TileMap\n}\n\nfunc (l *Level) LoadTileMap() {\n\tparsed := ParseTileMapString(l.TileMapString)\n\tl.LoadTileMapCells(parsed)\n}\n\n// row, length\nfunc (l *Level) GetTileMapDimensions() (int, int) {\n\tparsed := ParseTileMapString(l.TileMapString)\n\trowLength := len(parsed[0])\n\tcolumnLength := len(parsed)\n\treturn rowLength, columnLength\n}\n\nfunc (l *Level) InitDefaults() {\n\t// set default quit functions\n\texitTerms := []string{\"q\", \"quit\", \"exit\"}\n\n\tif l.ColonLineCallbacks == nil {\n\t\tl.ColonLineCallbacks = make(map[string]func(*Game))\n\t}\n\n\tfor _, term := range exitTerms {\n\n\t\tif _, ok := l.ColonLineCallbacks[term]; !ok {\n\t\t\tl.ColonLineCallbacks[term] = func(g *Game) {\n\t\t\t\tgo func() {\n\t\t\t\t\tg.VimManEvents <- VimManEvent{Content: \"exit\"}\n\t\t\t\t}()\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "level1_basic_movement.go",
    "content": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\nconst LevelBasicMovementTileMapString = `\n+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n  Try and find the exit |        |        |     |     |     |\n+  +--+--+--+--+--+  +--+  +--+  +  +  +  +  +  +  +  +--+  +\n|           |     |     |     |  |  |  |     |  |  |  |     |\n+--+--+  +--+  +  +--+  +--+  +  +--+  +  +--+  +  +  +  +  +\n|        |     |     |     |  |     |  |     |     |     |  |\n+  +--+--+  +  +--+--+--+  +--+--+  +  +--+--+--+--+--+--+  +\n|  |     |  |  |           |        |           |     |     |\n+  +  +  +  +--+  +--+--+--+  +--+--+--+--+--+  +  +  +  +--+\n|     |  |  |     |     |     |     |        |     |  |     |\n+--+--+  +  +  +--+  +  +  +  +--+  +  +--+  +--+--+  +--+  +\n|     |  |     |     |     |        |  |  |  |     |  |     |\n+  +  +  +--+  +  +--+  +--+  +--+--+  +  +  +  +--+  +  +--+\n|  |  |     |  |  |     |  |  |        |  |        |  |     |\n+  +  +--+  +  +  +--+--+  +  +  +--+--+  +--+--+  +  +--+--+\n|  |        |  |     |     |           |  |     |  |        |\n+  +--+--+--+--+--+  +  +  +--+--+--+  +  +  +  +  +--+--+  +\n|  |                 |  |  |     |     |     |  |           |\n+  +--+  +--+  +--+--+  +  +  +  +  +--+--+  +  +--+--+--+--+\n|        |     |        |     |              |         exit ↓ \n+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n`\n\nfunc NewLevelBasicMovement(g *Game) *Level {\n\t// create user\n\tuser := NewUser(g.Stage, 1, 1)\n\tvar entities []Renderer\n\tentities = append(entities, user)\n\n\ttileData := TileMapCellDataMap{\n\t\t'↓': TileMapCellData{\n\t\t\tCh:                 '↓',\n\t\t\tFgColor:            termbox.ColorGreen,\n\t\t\tBgColor:            termbox.ColorBlack,\n\t\t\tCollidesPhysically: false,\n\t\t\tCollisionCallback: func() {\n\t\t\t\tlevelInstance := NewLevelExitingVim(g)\n\t\t\t\tg.Stage.SetLevel(levelInstance)\n\t\t\t},\n\t\t},\n\t}\n\n\tlevel := &Level{\n\t\tGame:          g,\n\t\tEntities:      entities,\n\t\tTileMapString: LevelBasicMovementTileMapString,\n\t\tTileData:      tileData,\n\t\tInputBlocked:  true,\n\t\tVimMode:       normalMode,\n\t\tInit: func() {\n\t\t\t// load info\n\t\t\ttitleOptions := WordOptions{\n\t\t\t\tInitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\ttitle := NewWord(g.Stage, levelTitleCoordX, levelTitleCoordY, \"Level 1 - MOVING THE CURSOR\", titleOptions)\n\n\t\t\texplanationOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\texplanation := NewWord(g.Stage, levelExplanationCoordX, levelExplanationCoordY, \"J: down, H: left, K: up, L: right\", explanationOptions)\n\n\t\t\tg.Stage.AddScreenEntity(title, explanation)\n\t\t},\n\t}\n\n\tlevel.InitDefaults()\n\treturn level\n}\n"
  },
  {
    "path": "level2_exiting_vim.go",
    "content": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\nconst LevelExitingVimTileMapString = `\n+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                            VIM                           |\n|~                  Hardest editor to exit                  |\n|~                            :)                            |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n`\n\nfunc NewLevelExitingVim(g *Game) *Level {\n\t// create user\n\tuser := NewUser(g.Stage, 1, 1)\n\tvar entities []Renderer\n\tentities = append(entities, user)\n\n\ttileData := TileMapCellDataMap{\n\t\t'↓': TileMapCellData{\n\t\t\tCh:                 '↓',\n\t\t\tFgColor:            termbox.ColorGreen,\n\t\t\tBgColor:            termbox.ColorBlack,\n\t\t\tCollidesPhysically: false,\n\t\t\tCollisionCallback:  func() {},\n\t\t},\n\t}\n\n\tlevel := &Level{\n\t\tGame:          g,\n\t\tEntities:      entities,\n\t\tTileMapString: LevelExitingVimTileMapString,\n\t\tTileData:      tileData,\n\t\tInputBlocked:  true,\n\t\tVimMode:       normalMode,\n\t\tInit: func() {\n\t\t\ttitleOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\ttitle := NewWord(g.Stage, levelTitleCoordX, levelTitleCoordY, \"Level 2 - EXITING VIM\", titleOptions)\n\n\t\t\texplanationOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\texplanation := NewWord(g.Stage, levelExplanationCoordX, levelExplanationCoordY, \"You can't be a great Vim user without knowing how to exit.\", explanationOptions)\n\n\t\t\thintOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\thint := NewWord(g.Stage, levelHintCoordX, levelHintCoordY, \"Type colon ':', then 'q', press enter\", hintOptions)\n\n\t\t\tg.Stage.AddScreenEntity(title, explanation, hint)\n\t\t},\n\t\tColonLineCallbacks: make(map[string]func(*Game)),\n\t}\n\n\texitTerms := []string{\"q\", \"quit\", \"exit\"}\n\tfor _, term := range exitTerms {\n\t\tif _, ok := level.ColonLineCallbacks[term]; !ok {\n\t\t\tlevel.ColonLineCallbacks[term] = func(g *Game) {\n\t\t\t\tlevelInstance := NewLevelTextEditing(g)\n\t\t\t\tg.Stage.SetLevel(levelInstance)\n\t\t\t}\n\t\t}\n\t}\n\n\tlevel.InitDefaults()\n\treturn level\n}\n"
  },
  {
    "path": "level3_text_editing.go",
    "content": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\nconst LevelTextEditingTileMapString = `\n+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n|~                                                          |\n|~                                                          |\n|~                                                          |\n|~  1- DELETION - Press \"x\" to delete the character under   |\n|~              the cursor  in normal mode.                 |\n|~                                                          |\n|~               Yyou shalll noot paass                     |\n|~                                                          |\n|~  2- INSERTION - Move the cursor after the character      |\n|~  where the text should be inserted, press i and type     |\n|~                                                          |\n|~                   Yu shll nt pss                         |\n|~                                                          |\n|~  3- APPENDING - Move the curser before the character     |\n|~  where the text should be appended, press a and type     |\n|~                                                          |\n|~                    Yo shal no pas                        |\n|~                                                          |\n|~                                                          |\n|~                                                          |\n+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n`\n\nfunc NewLevelTextEditing(g *Game) *Level {\n\tuser := NewUser(g.Stage, 1, 1)\n\tvar entities []Renderer\n\tentities = append(entities, user)\n\n\ttileData := TileMapCellDataMap{\n\t\t'↓': TileMapCellData{\n\t\t\tCh:                 '↓',\n\t\t\tFgColor:            termbox.ColorGreen,\n\t\t\tBgColor:            termbox.ColorBlack,\n\t\t\tCollidesPhysically: false,\n\t\t\tCollisionCallback:  func() {},\n\t\t},\n\t}\n\n\tlevel := &Level{\n\t\tGame:          g,\n\t\tEntities:      entities,\n\t\tTileMapString: LevelTextEditingTileMapString,\n\t\tTileData:      tileData,\n\t\tVimMode:       normalMode,\n\t\tInit: func() {\n\t\t\ttitleOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\ttitle := NewWord(g.Stage, levelTitleCoordX, levelTitleCoordY, \"Level 3 - TEXT EDITING\", titleOptions)\n\n\t\t\texplanationOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\texplanation := NewWord(g.Stage, levelExplanationCoordX, levelExplanationCoordY, \"Delete, insert, append.\", explanationOptions)\n\n\t\t\thintOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\thint := NewWord(g.Stage, levelHintCoordX, levelHintCoordY, \"Complete the 3 steps below to proceed to the next level.\", hintOptions)\n\n\t\t\tg.Stage.AddScreenEntity(title, explanation, hint)\n\t\t},\n\t}\n\n\tlevel.InitDefaults()\n\treturn level\n}\n"
  },
  {
    "path": "level4_bomberman.go",
    "content": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"time\"\n)\n\nconst LevelBombermanTileMapString = `\n▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅\n█      ☵☲     ☵☲    █\n█☲◼◼ ◼◼ ◼◼ ◼◼ ◼◼ ◼◼ █\n█   ☲☵☲☵            █\n█ ◼◼☲◼◼ ◼◼ ◼◼ ◼◼ ◼◼ █\n█    ☲☵      ☵☲☵    █\n█ ◼◼ ◼◼ ◼◼ ◼◼ ◼◼ ◼◼☲█\n█☲☵      ☲☵   ☲☵  ☲☵█\n█ ◼◼☵◼◼ ◼◼ ◼◼ ◼◼ ◼◼☵█\n█           ☲☵ exit ↓\n▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\n`\n\nfunc NewLevelBomberman(g *Game) *Level {\n\n\tuser := NewUser(g.Stage, 1, 1)\n\tvar entities []Renderer\n\tentities = append(entities, user)\n\ttileData := TileMapCellDataMap{\n\t\t'b': TileMapCellData{\n\t\t\tCh:                 '💣',\n\t\t\tFgColor:            termbox.ColorGreen,\n\t\t\tBgColor:            termbox.ColorBlack,\n\t\t\tCollidesPhysically: true,\n\t\t\tCollisionCallback:  nil,\n\t\t\tInitCallback: func(selfEntity *Entity) {\n\t\t\t\tbombOptions := WordOptions{InitCallback: nil, Fg: typedCharacterFg, Bg: typedCharacterBg, CollidesPhysically: true}\n\t\t\t\tbomb := NewWord(g.Stage, selfEntity.GetPositionX(), selfEntity.GetPositionY(), string('💣'), bombOptions)\n\t\t\t\tg.Stage.AddTypedEntity(bomb)\n\n\t\t\t\tgo func() {\n\t\t\t\t\t<-time.After(1 * time.Second)\n\t\t\t\t\tposX := selfEntity.Position.x\n\t\t\t\t\tposY := selfEntity.Position.y\n\t\t\t\t\tpositions := [][2]int{\n\t\t\t\t\t\t{posX, posY},\n\t\t\t\t\t\t{posX + 1, posY},\n\t\t\t\t\t\t{posX, posY + 1},\n\t\t\t\t\t\t{posX - 1, posY},\n\t\t\t\t\t\t{posX, posY - 1},\n\t\t\t\t\t}\n\n\t\t\t\t\tvar positionsToBeCleared [][2]int\n\n\t\t\t\t\tfor _, pos := range positions {\n\t\t\t\t\t\tif !g.Stage.Canvas.IsInsideOfBoundaries(pos[0], pos[1]) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// deliberately using reverse order in two dimensional array :/\n\t\t\t\t\t\tif !ContainsRune([]rune{'◼', '▅', '█'}, g.Stage.LevelInstance.TileMap[pos[1]][pos[0]].Ch) {\n\t\t\t\t\t\t\tpositionsToBeCleared = append(positionsToBeCleared, [2]int{pos[0], pos[1]})\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// clear character and collision\n\t\t\t\t\tg.Stage.ClearTileMapCellsAt(positionsToBeCleared)\n\t\t\t\t}()\n\t\t\t},\n\t\t},\n\t\t'↓': TileMapCellData{\n\t\t\tCh:                 '↓',\n\t\t\tFgColor:            termbox.ColorGreen,\n\t\t\tBgColor:            termbox.ColorBlack,\n\t\t\tCollidesPhysically: false,\n\t\t\tCollisionCallback: func() {\n\t\t\t\tlevelInstance := NewLevelExitingVim(g)\n\t\t\t\tg.Stage.SetLevel(levelInstance)\n\t\t\t},\n\t\t},\n\t}\n\n\tlevel := &Level{\n\t\tGame:                 g,\n\t\tEntities:             entities,\n\t\tTileMapString:        LevelBombermanTileMapString,\n\t\tTileData:             tileData,\n\t\tInputRunes:           []rune{'b'},\n\t\tBlockedKeys:          []termbox.Key{termbox.KeyBackspace, termbox.KeyDelete},\n\t\tVimMode:              normalMode,\n\t\tTextShiftingDisabled: true,\n\t\tInit: func() {\n\t\t\ttitleOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\ttitle := NewWord(g.Stage, levelTitleCoordX, levelTitleCoordY, \"Level 4 - VIMBERMAN\", titleOptions)\n\n\t\t\texplanationOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\texplanation := NewWord(g.Stage, levelExplanationCoordX, levelExplanationCoordY, \"i: Insert Mode, esc: Back to Normal Mode\", explanationOptions)\n\n\t\t\thintOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}\n\t\t\thint := NewWord(g.Stage, levelHintCoordX, levelHintCoordY, \"Type b in Insert Mode to drop a bomb!\", hintOptions)\n\n\t\t\tg.Stage.AddScreenEntity(title, explanation, hint)\n\t\t},\n\t}\n\n\tlevel.InitDefaults()\n\treturn level\n}\n"
  },
  {
    "path": "logger.go",
    "content": "package vimman\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"sync\"\n)\n\ntype Logger struct {\n\tlogs []string\n}\n\nvar instance *Logger\nvar once sync.Once\n\nvar lg = GetLogger()\n\nfunc GetLogger() *Logger {\n\tonce.Do(func() {\n\t\tinstance = &Logger{\n\t\t\tlogs: make([]string, 0),\n\t\t}\n\t})\n\treturn instance\n}\n\nfunc (l *Logger) Log(strings ...string) {\n\tl.logs = append(l.logs, strings...)\n}\n\nfunc (l *Logger) LogValue(values ...interface{}) {\n\tvar strings []string\n\tfor _, string := range values {\n\t\tvalue := fmt.Sprintf(\"%v\", string)\n\t\tstrings = append(strings, value)\n\t}\n\tl.logs = append(l.logs, strings...)\n}\n\nfunc (l *Logger) DumpLogs() {\n\tfor _, log := range l.logs {\n\t\tfmt.Println(log)\n\t}\n}\n\nfunc (l *Logger) WriteFile(text string) {\n\tif os.Getenv(\"DEBUG\") == \"1\" {\n\t\tf, err := os.OpenFile(\"logfile.txt\", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"error opening file: %v\", err)\n\t\t}\n\t\tdefer f.Close()\n\n\t\tlog.SetOutput(f)\n\t\tlog.Println(text)\n\t}\n}\n"
  },
  {
    "path": "stage.go",
    "content": "package vimman\n\nimport (\n\t\"fmt\"\n\t\"github.com/nsf/termbox-go\"\n\t\"time\"\n)\n\nvar levelConstructors = []func(*Game) *Level{\n\tNewLevelBasicMovement,\n\tNewLevelExitingVim,\n\tNewLevelTextEditing,\n\tNewLevelBomberman,\n}\n\n// holds the current level meta data, canvas and entities\ntype Stage struct {\n\tGame           *Game\n\tLevel          int\n\tLevelInstance  *Level\n\tFps            float64\n\tCanvasEntities []Renderer // entities to be rendered in canvas\n\tScreenEntities []Renderer // entities to be rendered outside of the canvas\n\tTypedEntities  []Renderer\n\tBgCell         *termbox.Cell\n\tCanvas         Canvas\n\tWidth          int\n\tHeight         int\n\tpixelMode      bool\n\toffsetx        int\n\toffsety        int\n\tModeLabel      *Word\n\tColonLine      *Word\n}\n\nfunc NewStage(g *Game, level int, fps float64, bgCell *termbox.Cell) *Stage {\n\treturn &Stage{\n\t\tGame:           g,\n\t\tLevel:          level,\n\t\tFps:            fps,\n\t\tCanvasEntities: nil,\n\t\tScreenEntities: nil,\n\t\tTypedEntities:  nil,\n\t\tBgCell:         bgCell,\n\t\tCanvas:         nil,\n\t\tWidth:          0,\n\t\tHeight:         0,\n\t\tpixelMode:      false,\n\t\toffsetx:        0,\n\t\toffsety:        0,\n\t}\n}\n\nfunc (s *Stage) AddCanvasEntity(e ...Renderer) {\n\ts.CanvasEntities = append(s.CanvasEntities, e...)\n}\n\nfunc (s *Stage) AddScreenEntity(e ...Renderer) {\n\ts.ScreenEntities = append(s.ScreenEntities, e...)\n}\n\nfunc (s *Stage) AddTypedEntity(e ...Renderer) {\n\ts.TypedEntities = append(s.TypedEntities, e...)\n}\n\nfunc (s *Stage) ClearCanvasEntities() {\n\ts.CanvasEntities = nil\n\ts.ScreenEntities = nil\n\ts.TypedEntities = nil\n}\n\nfunc (s *Stage) SetGame(game *Game) {\n\ts.Game = game\n}\n\n// this function handles all the rendering\n// sets and renders in order:\n// tilemap cells: the background cells\n// canvas entities: canvas entities, they have update methods that gets called on stage.update method\n// screen cells: cells outside of the canvas such as level label and instructions\nfunc (s *Stage) Render() {\n\ts.SetCanvasBackgroundCells()\n\n\tfor i, _ := range s.CanvasEntities {\n\t\te := s.CanvasEntities[i]\n\t\t// sets the cells for the entity, so that it overwrites the background cell(s)\n\t\te.SetCells(s)\n\t}\n\n\ts.TermboxSetScreenCells()\n\ts.TermboxSetCanvasCells()\n\ts.TermboxSetTypedCells()\n\ts.TermboxSetCursorCell()\n\ttermbox.Flush()\n}\n\nfunc (s *Stage) update(ev termbox.Event, delta time.Duration) {\n\tfor _, e := range s.CanvasEntities {\n\t\te.Update(s, ev, delta)\n\t}\n\n\ts.updateModeLabel()\n\ts.updateColonLine()\n}\n\nfunc (s *Stage) updateModeLabel() {\n\ts.ModeLabel.Content = fmt.Sprintf(\"-- %s MODE --\", s.LevelInstance.VimMode)\n\ts.ModeLabel.Entity.Cells = ConvertStringToCells(s.ModeLabel.Content, s.ModeLabel.Fg, s.ModeLabel.Bg)\n}\n\nfunc (s *Stage) updateColonLine() {\n\ts.ColonLine.Entity.Cells = ConvertStringToCells(s.ColonLine.Content, s.ColonLine.Fg, s.ColonLine.Bg)\n}\n\nfunc (s *Stage) Init() {\n\tlevel := s.Level - 1\n\tif level < 0 || level > len(levelConstructors) {\n\t\tlevel = 0\n\t}\n\n\ts.SetLevel(levelConstructors[level](s.Game))\n}\n\nfunc (s *Stage) SetLevel(levelInstance *Level) {\n\ts.Reset()\n\ts.LevelInstance = levelInstance\n\ts.LevelInstance.Init()\n\ts.LevelInstance.LoadTileMap()\n\ts.Resize(s.LevelInstance.GetTileMapDimensions())\n\n\tfor _, e := range s.LevelInstance.Entities {\n\t\ts.AddCanvasEntity(e)\n\t}\n\n\tmodeLabelOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: false, Tags: []Tag{{\"ModeLabel\"}}}\n\ts.ModeLabel = NewWord(s, 0, s.Game.getScreenSizeY()-2, fmt.Sprintf(\"-- %s MODE --\", levelInstance.VimMode), modeLabelOptions)\n\n\tcolonLineOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: false, Tags: []Tag{{\"ColonLine\"}}}\n\ts.ColonLine = NewWord(s, 0, s.Game.getScreenSizeY()-1, \"\", colonLineOptions)\n\n\ts.AddScreenEntity(s.ModeLabel, s.ColonLine)\n}\n\nfunc (s *Stage) Reset() {\n\ts.ClearCanvasEntities()\n\ts.Canvas = NewCanvas(10, 10)\n}\n\nfunc (s *Stage) Resize(w, h int) {\n\ts.Width = w\n\ts.Height = h\n\n\tif s.pixelMode {\n\t\ts.Height *= 2\n\t}\n\n\tif s.LevelInstance.Height != 0 && s.LevelInstance.Width != 0 {\n\t\ts.Width = s.LevelInstance.Width\n\t\ts.Height = s.LevelInstance.Height\n\t}\n\n\tc := NewCanvas(s.Width, s.Height)\n\n\t// Copy old data that fits\n\tfor i := 0; i < MinInt(s.Height, len(s.Canvas)); i++ {\n\t\tfor j := 0; j < MinInt(s.Width, len(s.Canvas)); j++ {\n\t\t\tc[i][j] = s.Canvas[i][j]\n\t\t}\n\t}\n\ts.Canvas = c\n}\n\nfunc (s *Stage) GetDefaultBgCell() *TermBoxCell {\n\treturn &TermBoxCell{s.BgCell, false, TileMapCellData{}}\n}\n\nfunc (s *Stage) GetRendererEntityByTag(wantedTag Tag) Renderer {\n\tfor _, ce := range s.CanvasEntities {\n\t\tfor _, tag := range ce.GetTags() {\n\t\t\tif tag.Name == wantedTag.Name {\n\t\t\t\treturn ce\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// sets the background cells to be rendered, this gets rendered first in the render method\n// so that other cells can be overwritten into the same location\nfunc (s *Stage) SetCanvasBackgroundCells() {\n\tfor i, row := range s.Canvas {\n\t\tfor j, _ := range row {\n\t\t\tif s.LevelInstance.TileMap[i][j].Cell != nil {\n\t\t\t\t// insert tile map cell\n\t\t\t\ts.Canvas.SetCellAt(i, j, s.LevelInstance.TileMap[i][j])\n\t\t\t} else {\n\t\t\t\t//insert default bg cell\n\t\t\t\ts.Canvas.SetCellAt(i, j, s.GetDefaultBgCell())\n\t\t\t}\n\t\t}\n\t}\n}\n\n// calls termbox.setCell, sets the coordinates and the cell attributes\n// this does the actual rendering of the characters, thanks to termbox-go <3\nfunc (s *Stage) TermboxSetCell(x, y int, cell *TermBoxCell, offset bool) {\n\tif offset {\n\t\toffsetX, offsetY := s.LevelInstance.GetScreenOffset()\n\t\tx += offsetX\n\t\ty += offsetY\n\t}\n\n\ttermbox.SetCell(x, y, cell.Ch,\n\t\ttermbox.Attribute(cell.Fg),\n\t\ttermbox.Attribute(cell.Bg))\n}\n\n// sets the cells inside the canvas, offset is being applied in order to keep the canvas in center\nfunc (s *Stage) TermboxSetCanvasCells() {\n\tfor i, row := range s.Canvas {\n\t\tfor j, _ := range row {\n\t\t\tcell := row[j]\n\t\t\t// intentionally use j,i in reverse order\n\t\t\ts.TermboxSetCell(j, i, cell, true)\n\t\t}\n\t}\n}\n\n// sets the cells outside of the canvas, no offset is being applied\nfunc (s *Stage) TermboxSetScreenCells() {\n\tfor _, e := range s.ScreenEntities {\n\t\tfor j, _ := range e.GetCells() {\n\t\t\tcell := e.GetCells()[j]\n\t\t\tx := e.GetPositionX()\n\t\t\toffsetX := 0\n\n\t\t\tif e.ShouldCenterHorizontally() {\n\t\t\t\toffsetX, _ = e.GetScreenOffset()\n\t\t\t}\n\n\t\t\tx = x + j + offsetX\n\t\t\ts.TermboxSetCell(x, e.GetPositionY(), cell, false)\n\t\t}\n\t}\n}\n\nfunc (s *Stage) TermboxSetTypedCells() {\n\tfor _, e := range s.TypedEntities {\n\t\tfor j, _ := range e.GetCells() {\n\t\t\tcell := e.GetCells()[j]\n\t\t\ts.TermboxSetCell(e.GetPositionX(), e.GetPositionY(), cell, true)\n\t\t}\n\t}\n}\n\nfunc (s *Stage) TermboxSetCursorCell() {\n\tcursorEntity := s.GetRendererEntityByTag(Tag{\"Cursor\"})\n\tfor j, _ := range cursorEntity.GetCells() {\n\t\tcell := cursorEntity.GetCells()[j]\n\t\ts.TermboxSetCell(cursorEntity.GetPositionX(), cursorEntity.GetPositionY(), cell, true)\n\t}\n}\n\nfunc (s *Stage) CheckCollision(x, y int) bool {\n\treturn s.Canvas.CheckCollision(x, y)\n}\n\n// clear Canvas cell and TileMap cell at the given positions\nfunc (s *Stage) ClearTileMapCellsAt(positions [][2]int) {\n\tfor _, pos := range positions {\n\t\toptions := DefaultWordOptions()\n\t\temptyChar := NewEmptyCharacter(s, pos[0], pos[1], options)\n\t\ts.AddTypedEntity(emptyChar)\n\t\ts.LevelInstance.TileMap[pos[1]][pos[0]].collidesPhysically = false\n\t}\n}\n"
  },
  {
    "path": "termbox_cell.go",
    "content": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\ntype TermBoxCell struct {\n\t*termbox.Cell\n\tcollidesPhysically bool\n\tcellData           TileMapCellData\n}\n\nfunc EmptyTileMapCell() *TermBoxCell {\n\tdata := CommonTileMapCellData[' ']\n\tcell := &TermBoxCell{\n\t\t&termbox.Cell{data.Ch, data.FgColor, data.BgColor},\n\t\tdata.CollidesPhysically,\n\t\tdata,\n\t}\n\n\treturn cell\n}\n"
  },
  {
    "path": "tilemap.go",
    "content": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"strings\"\n)\n\nfunc NewTileMapCell(ch rune, fn func(), lineNumber int) TileMapCellData {\n\treturn TileMapCellData{\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 ch,\n\t\tCollidesPhysically: false,\n\t\tCollisionCallback:  fn,\n\t\tLineNumber:         lineNumber,\n\t}\n}\n\ntype TileMapCellData struct {\n\tCh                 rune\n\tBgColor            termbox.Attribute\n\tFgColor            termbox.Attribute\n\tCollidesPhysically bool\n\tCollisionCallback  func()\n\tInitCallback       func(*Entity)\n\tLineNumber         int\n}\n\ntype TileMapCellDataMap map[rune]TileMapCellData\n\nvar CommonTileMapCellData = TileMapCellDataMap{\n\t'0': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 ' ',\n\t\tCollidesPhysically: false,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'↓': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '↓',\n\t\tCollidesPhysically: false,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'+': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '+',\n\t\tCollidesPhysically: true,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'-': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '-',\n\t\tCollidesPhysically: true,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'|': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '|',\n\t\tCollidesPhysically: true,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'█': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '█',\n\t\tCollidesPhysically: true,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'◼': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '◼',\n\t\tCollidesPhysically: true,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'▅': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '▅',\n\t\tCollidesPhysically: true,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'▀': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '▀',\n\t\tCollidesPhysically: true,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'☵': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '☵',\n\t\tCollidesPhysically: true,\n\t\tCollisionCallback:  func() {},\n\t},\n\t'☲': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 '☲',\n\t\tCollidesPhysically: true,\n\t\tCollisionCallback:  func() {},\n\t},\n\t' ': {\n\t\tBgColor:            termbox.ColorBlack,\n\t\tFgColor:            termbox.ColorWhite,\n\t\tCh:                 ' ',\n\t\tCollidesPhysically: false,\n\t\tCollisionCallback:  func() {},\n\t},\n}\n\nfunc ParseLine(l string) []rune {\n\tvar lineChars []rune\n\n\t//chars := strings.Split(l, \" \")\n\t//line := strings.Join(chars, \"\")\n\n\tfor _, char := range l {\n\t\tlineChars = append(lineChars, char)\n\t}\n\n\treturn lineChars\n}\n\nfunc ParseTileMapString(tileMap string) [][]rune {\n\tvar parsed [][]rune\n\n\tlines := strings.Split(tileMap, \"\\n\")\n\tlines = lines[1 : len(lines)-1]\n\n\tfor _, line := range lines {\n\t\tl := ParseLine(line)\n\t\tparsed = append(parsed, l)\n\t}\n\n\treturn parsed\n}\n"
  },
  {
    "path": "user.go",
    "content": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"reflect\"\n\t\"time\"\n)\n\ntype Class int\n\ntype User struct {\n\t*Entity\n}\n\nfunc (u *User) ShouldCenterHorizontally() bool {\n\treturn false\n}\n\nfunc NewUser(s *Stage, x, y int) (u *User) {\n\tcells := []*TermBoxCell{\n\t\t{&termbox.Cell{'▒', termbox.ColorGreen, bgColor}, false, TileMapCellData{}},\n\t}\n\n\ttags := []Tag{{\"Cursor\"}}\n\tentityOptions := EntityOptions{9999, tags, nil}\n\te := NewEntity(s, x, y, 1, 1, ' ', termbox.ColorBlue, termbox.ColorWhite, cells, false, entityOptions)\n\tu = &User{\n\t\tEntity: e,\n\t}\n\treturn\n}\n\nfunc (u *User) handleNormalModeEvents(s *Stage, event termbox.Event) {\n\tswitch event.Ch {\n\tcase 'k':\n\t\tnextY := u.GetPositionY() - 1\n\t\tif !s.CheckCollision(u.GetPositionX(), nextY) {\n\t\t\tu.SetPositionY(nextY)\n\t\t}\n\tcase 'j':\n\t\tnextY := u.GetPositionY() + 1\n\t\tif !s.CheckCollision(u.GetPositionX(), nextY) {\n\t\t\tu.SetPositionY(nextY)\n\t\t}\n\tcase 'l':\n\t\tnextX := u.GetPositionX() + 1\n\t\tif !s.CheckCollision(nextX, u.GetPositionY()) {\n\t\t\tu.SetPositionX(nextX)\n\t\t}\n\tcase 'h':\n\t\tnextX := u.GetPositionX() - 1\n\t\tif !s.CheckCollision(nextX, u.GetPositionY()) {\n\t\t\tu.SetPositionX(nextX)\n\t\t}\n\tcase 'i':\n\t\tif s.LevelInstance.VimMode != insertMode && !s.LevelInstance.InputBlocked {\n\t\t\ts.LevelInstance.VimMode = insertMode\n\t\t}\n\n\tcase 'x':\n\t\tif ContainsTermboxKey(s.LevelInstance.BlockedKeys, termbox.KeyDelete) {\n\t\t\treturn\n\t\t}\n\n\t\tif s.LevelInstance.InputBlocked {\n\t\t\treturn\n\t\t}\n\n\t\tx := u.GetPositionX()\n\t\ty := u.GetPositionY()\n\n\t\t// keep the last element in place, insert an empty cell before the last character in the line\n\t\ttileMap := s.LevelInstance.TileMap\n\t\tlastElement := s.LevelInstance.TileMap[y][len(s.LevelInstance.TileMap[y])-1]\n\t\ttileMap[y] = append(tileMap[y][:x], tileMap[y][x+1:len(tileMap[y])-1]...)\n\t\ttileMap[y] = append(tileMap[y], EmptyTileMapCell(), lastElement)\n\n\tcase ':':\n\t\tif s.LevelInstance.VimMode == normalMode {\n\t\t\ts.LevelInstance.VimMode = colonMode\n\t\t\ts.ColonLine.Content = \":\"\n\t\t}\n\t}\n}\n\nfunc (u *User) handleInsertModeEvents(s *Stage, event termbox.Event) {\n\t// return on empty event\n\tif reflect.DeepEqual(event, termbox.Event{}) {\n\t\treturn\n\t}\n\n\tswitch event.Key {\n\t// switch to normal mode on esc key event\n\tcase termbox.KeyEsc:\n\t\ts.LevelInstance.VimMode = normalMode\n\t\treturn\n\tcase termbox.KeyBackspace, termbox.KeyBackspace2:\n\t\tif ContainsTermboxKey(s.LevelInstance.BlockedKeys, termbox.KeyBackspace) ||\n\t\t\tContainsTermboxKey(s.LevelInstance.BlockedKeys, termbox.KeyBackspace2) {\n\t\t\treturn\n\t\t}\n\n\t\tcharacterOptions := WordOptions{InitCallback: nil, Fg: typedCharacterFg, Bg: typedCharacterBg}\n\t\tcharacter := NewEmptyCharacter(s, u.GetPositionX()-1, u.GetPositionY(), characterOptions)\n\t\tif !character.IsInsideOfCanvasBoundaries() {\n\t\t\treturn\n\t\t}\n\n\t\ts.AddTypedEntity(character)\n\t\tu.SetPositionX(u.GetPositionX() - 1)\n\tdefault:\n\t\t// don't allow non-character events\n\t\tif event.Ch == 0 {\n\t\t\treturn\n\t\t}\n\n\t\t// check if rune input is allowed\n\t\tif len(s.LevelInstance.InputRunes) > 0 {\n\t\t\tif !ContainsRune(s.LevelInstance.InputRunes, event.Ch) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tcharacterOptions := WordOptions{InitCallback: nil, Fg: typedCharacterFg, Bg: typedCharacterBg}\n\t\tcharacter := NewWord(s, u.GetPositionX(), u.GetPositionY(), string(event.Ch), characterOptions)\n\t\tif !character.IsInsideOfCanvasBoundaries() {\n\t\t\treturn\n\t\t}\n\n\t\tif !s.LevelInstance.TextShiftingDisabled {\n\t\t\tx := u.GetPositionX()\n\t\t\ty := u.GetPositionY()\n\t\t\ttileMap := s.LevelInstance.TileMap[y]\n\t\t\tlastElement := tileMap[len(tileMap)-1]\n\t\t\ttileMap = append(tileMap[:x], append([]*TermBoxCell{character.Cell}, tileMap[x:len(tileMap)-2]...)...)\n\t\t\ttileMap = append(tileMap, lastElement)\n\t\t}\n\n\t\t// type a character and add as typed entity\n\t\ts.AddTypedEntity(character)\n\t\tu.SetPositionX(u.GetPositionX() + 1)\n\n\t\t// at the end of the current line, move the cursor next line\n\t\tif s.Canvas.IsInLastColumn(u.GetPositionX()) {\n\t\t\tu.SetPosition(1, u.GetPositionY()+1)\n\t\t}\n\n\t\tif character.InitCallback != nil {\n\t\t\tcharacter.InitCallback()\n\t\t}\n\n\t\tif s.LevelInstance.TileData[event.Ch].InitCallback != nil {\n\t\t\ts.LevelInstance.TileData[event.Ch].InitCallback(character.Entity)\n\t\t}\n\t}\n}\n\nfunc (u *User) handleColonModeEvents(s *Stage, event termbox.Event) {\n\tif event.Key == termbox.KeyEnter {\n\t\ts.LevelInstance.VimMode = normalMode\n\n\t\tif len(s.LevelInstance.ColonLineCallbacks) > 0 {\n\t\t\tif fn, ok := s.LevelInstance.ColonLineCallbacks[s.ColonLine.Content[1:]]; ok {\n\t\t\t\tfn(s.Game)\n\t\t\t}\n\t\t}\n\t}\n\n\tif event.Ch != 0 {\n\t\ts.ColonLine.Content = s.ColonLine.Content + string(event.Ch)\n\t}\n}\n\nfunc (u *User) Update(s *Stage, event termbox.Event, delta time.Duration) {\n\tswitch s.LevelInstance.VimMode {\n\tcase colonMode:\n\t\tu.handleColonModeEvents(s, event)\n\tcase normalMode:\n\t\tu.handleNormalModeEvents(s, event)\n\tcase insertMode:\n\t\tu.handleInsertModeEvents(s, event)\n\t}\n}\n"
  },
  {
    "path": "utils.go",
    "content": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\nfunc ContainsRune(s []rune, e rune) bool {\n\tfor _, a := range s {\n\t\tif a == e {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc ContainsString(s []string, e string) bool {\n\tfor _, a := range s {\n\t\tif a == e {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc ContainsTermboxKey(s []termbox.Key, e termbox.Key) bool {\n\tfor _, a := range s {\n\t\tif a == e {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc MinInt(a, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "word.go",
    "content": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"time\"\n)\n\ntype Word struct {\n\t*Entity\n\tContent            string\n\tSpeed              float64\n\tDirection          Direction\n\tCenterHorizontally bool\n\tFg                 termbox.Attribute\n\tBg                 termbox.Attribute\n}\n\nfunc (w *Word) ShouldCenterHorizontally() bool {\n\treturn w.CenterHorizontally\n}\n\ntype WordOptions struct {\n\tInitCallback       func()\n\tFg                 termbox.Attribute\n\tBg                 termbox.Attribute\n\tCollidesPhysically bool\n\tCenterHorizontally bool\n\tTags               []Tag\n}\n\nfunc ConvertStringToCells(s string, fg termbox.Attribute, bg termbox.Attribute) []*TermBoxCell {\n\tvar arr []*TermBoxCell\n\n\tfor i := 0; i < len([]rune(s)); i++ {\n\t\tcell := &TermBoxCell{\n\t\t\tCell: &termbox.Cell{\n\t\t\t\t[]rune(s)[i],\n\t\t\t\tfg,\n\t\t\t\tbg,\n\t\t\t},\n\t\t\tcollidesPhysically: false,\n\t\t\tcellData:           TileMapCellData{}}\n\n\t\tarr = append(arr, cell)\n\t}\n\n\treturn arr\n}\n\nfunc NewWord(s *Stage, x, y int, content string, options WordOptions) *Word {\n\tfg, bg, collidesPhysically, centerHorizontally := options.Fg, options.Bg, options.CollidesPhysically, options.CenterHorizontally\n\n\tcells := ConvertStringToCells(content, fg, bg)\n\tentityOptions := EntityOptions{2000, options.Tags, nil}\n\te := NewEntity(s, x, y, len(content), 1, ' ', fg, bg, cells, collidesPhysically, entityOptions)\n\treturn &Word{\n\t\tEntity:             e,\n\t\tContent:            content,\n\t\tDirection:          horizontal,\n\t\tCenterHorizontally: centerHorizontally,\n\t\tFg:                 fg,\n\t\tBg:                 bg,\n\t}\n}\n\nfunc DefaultWordOptions() WordOptions {\n\treturn WordOptions{InitCallback: nil, Fg: typedCharacterFg, Bg: typedCharacterBg, CollidesPhysically: false, CenterHorizontally: true}\n}\n\nfunc NewEmptyCharacter(s *Stage, x, y int, options WordOptions) *Word {\n\treturn NewWord(s, x, y, string(\" \"), options)\n}\n\nfunc (w *Word) Update(s *Stage, event termbox.Event, delta time.Duration) {\n}\n"
  }
]