Full Code of ozankasikci/vim-man for AI

master 378b6360890e cached
23 files
50.8 KB
15.9k tokens
139 symbols
1 requests
Download .txt
Repository: ozankasikci/vim-man
Branch: master
Commit: 378b6360890e
Files: 23
Total size: 50.8 KB

Directory structure:
gitextract_a3ra5gw2/

├── .gitignore
├── LICENSE
├── README.md
├── canvas.go
├── canvas_test.go
├── cmd/
│   └── console/
│       └── vimman.go
├── docs/
│   └── LEVEL_PLANNING.md
├── entity.go
├── game.go
├── go.mod
├── go.sum
├── level.go
├── level1_basic_movement.go
├── level2_exiting_vim.go
├── level3_text_editing.go
├── level4_bomberman.go
├── logger.go
├── stage.go
├── termbox_cell.go
├── tilemap.go
├── user.go
├── utils.go
└── word.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea
.DS_Store
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
logfile.txt
.swp


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Ozan Kaşıkçı

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# VimMan

Learn how to use Vim in its natural environment, the Terminal!
## About

VimMan 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! 

## Installation

`git clone https://github.com/ozankasikci/vim-man && cd vim-man`

`go run cmd/console/vimman.go`

to start from a specific level;

`LEVEL=2 go run cmd/console/vimman.go`

## Demo
### Level - 1 - Basic movement in Normal Mode

![](https://raw.githubusercontent.com/ozankasikci/ozankasikci.github.io/master/gifs/fantasia-level-1.gif)

### Level - 2 - How to exit Vim

![](https://raw.githubusercontent.com/ozankasikci/ozankasikci.github.io/master/gifs/fantasia-level-2.gif)

### Level - 3 - Basic text editing

![](https://raw.githubusercontent.com/ozankasikci/ozankasikci.github.io/master/gifs/fantasia-level-3.gif)

### Level - 4 - Vimberman!

![](https://raw.githubusercontent.com/ozankasikci/ozankasikci.github.io/master/gifs/fantasia-level-4.gif)

## TODO
* Add missing levels
  - [ ] File save
  - [ ] Deletion commands level (`dw`, `d$` vs)
  - [ ] Operators and motions
  - [ ] Using count
  - [ ] Operating on lines
  - [ ] Undo & redo
  - [ ] Put, replace, change operators
  - [ ] Search & substitute
  - [ ] Accessing Shell
* Handle edge cases in some levels
  - [ ] level 3 


================================================
FILE: canvas.go
================================================
package vimman

import "os"

//import tb "github.com/nsf/termbox-go"

type Canvas [][]*TermBoxCell

func NewCanvas(width, height int) Canvas {
	canvas := make(Canvas, height)
	for i := range canvas {
		canvas[i] = make([]*TermBoxCell, width)
	}
	return canvas
}

func (c Canvas) GetCellAt(x, y int) *TermBoxCell {
	return c[y][x]
}

func (c Canvas) CheckCollision(x, y int) bool {
	// check if out of boundaries
	if x < 0 || y < 0 || y >= len(c) || x >= len(c[0]) {
		return true
	}

	if c[y][x] == nil {
		return true
	}

	if c[y][x].cellData.CollisionCallback != nil {
		c[y][x].cellData.CollisionCallback()
	}

	if os.Getenv("DEBUG") == "1" {
		return false
	}

	return c[y][x].collidesPhysically
}

func (c *Canvas) OverWriteCanvasCell(x, y int, termboxCell *TermBoxCell) {
	if x >= 0 && x < len((*c)[0]) && y >= 0 && y < len((*c)) {
		// intentionally use x,y in reverse order
		(*c)[y][x] = termboxCell
	}
}

func (c *Canvas) SetCellAt(row, column int, cell *TermBoxCell) {
	(*c)[row][column] = cell
}

func (c *Canvas) IsInsideOfBoundaries(x, y int) bool {
	return x >= 0 && x < len((*c)[0])-1 && y >= 0 && y < len(*c)-1
}

func (c *Canvas) IsInLastColumn(x int) bool {
	return len((*c)[0])-1 == x
}


================================================
FILE: canvas_test.go
================================================
package vimman

import (
	"github.com/nsf/termbox-go"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestCanvasCheckCollision(t *testing.T) {
	x, y := 10, 10
	c := NewCanvas(x, y)
	c[1][1] = &TermBoxCell{&termbox.Cell{}, false, nil}
	c[0][0] = &TermBoxCell{&termbox.Cell{}, true, nil}

	tt := []struct {
		x      int
		y      int
		expect bool
	}{
		{-1, 0, true},
		{0, -1, true},
		{1, 1, false},
		{0, 0, true},
		{x, 0, true},
		{0, y, true},
	}

	for _, value := range tt {
		res := c.CheckCollision(value.x, value.y)
		assert.Equal(t, value.expect, res)
	}

	c[2][2] = nil
	assert.True(t, c.CheckCollision(2, 2))
}


================================================
FILE: cmd/console/vimman.go
================================================
package main

import (
	"github.com/ozankasikci/vim-man"
	"os"
	"strconv"
)

func main() {
	level := os.Getenv("LEVEL")
	levelInt, err := strconv.ParseInt(level, 10, 16)
	if err != nil {
		levelInt = 1
	}

	vimman.Init(int(levelInt))
}


================================================
FILE: docs/LEVEL_PLANNING.md
================================================
# Vim-Man Level Planning

## Level 1: Basic Movement

*   **Vim Concepts:** `h` (left), `j` (down), `k` (up), `l` (right).
*   **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.
*   **Visuals:** Clean, straightforward. Maybe a terminal-like aesthetic.
*   **Objective:** Reach the exit.

## Level 2: Exiting Vim (The First Challenge)

*   **Vim Concepts:** `:` (command mode), `q` (quit), `!` (force - for `q!`).
*   **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).
*   **Visuals:** The exit could be a flashing cursor or a representation of a closing window. "Dirty" tiles could have a distinct visual.
*   **Objective:** Successfully exit the level using the correct quit command.

## Level 3: Basic Text Editing (Deletion)

*   **Vim Concepts:** `x` (delete character under cursor), `dw` (delete word - maybe simplified to just deleting a 'word' entity).
*   **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.
*   **Visuals:** "Error characters" could be red `x`'s. "Bug words" could be small, distinct sprites.
*   **Objective:** Clear the path and reach the exit.

## Level 4: The Bomberman (Introduction to Insert Mode & Esc)

*   **Vim Concepts:** `i` (insert mode), `Esc` (return to normal mode), placing "bombs" (characters) in insert mode.
*   **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.
*   **Visuals:** Destructible blocks, bomb sprites, explosion effects.
*   **Objective:** Clear a path through destructible blocks and reach the exit.

## Level 5: The Word Jumper's Gauntlet

*   **Vim Concepts:** `w` (next word), `b` (previous word), `e` (end of word), `ge` (end of previous word).
*   **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.
*   **Visuals:** Platforms clearly look like "words". Gaps are obvious. Crumbling platforms could animate.
*   **Objective:** Navigate the platforms and reach the exit.

## Level 6: The Repetition Realm (Numeric Precision)

*   **Vim Concepts:** Using numbers with motion/commands (e.g., `3w`, `5j`, `2dd`).
*   **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.
*   **Visuals:** Gates could display the number required. Collectibles in clear numerical patterns.
*   **Objective:** Use numbered commands to overcome obstacles and reach the exit.

## Level 7: The Search & Find Expedition

*   **Vim Concepts:** `/` (search forward), `?` (search backward), `n` (next occurrence), `N` (previous occurrence).
*   **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.
*   **Visuals:** Maze environment. Searched items/paths could glow or have a special indicator.
*   **Objective:** Find the target(s) using search commands and reach the exit.

## Level 8: The Copy-Paste Assembly Line

*   **Vim Concepts:** `y` (yank/copy), `p` (paste after), `P` (paste before), Visual mode (`v` character-wise, `V` line-wise) for selecting text.
*   **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."
*   **Visuals:** Distinct "source" areas for yanking. A clear "build zone" where pasting occurs. Visual feedback for selection in Visual mode.
*   **Objective:** Assemble the target word/snippet and complete the level. 

================================================
FILE: entity.go
================================================
package vimman

import (
	"github.com/nsf/termbox-go"
	"time"
)

type Direction int

const (
	horizontal Direction = iota
	vertical
)

type Entity struct {
	Stage        *Stage
	Position     Point
	Width        int
	Height       int
	Rune         rune
	Cell         *TermBoxCell
	Cells        []*TermBoxCell
	DrawPriority int
	Tags         []Tag
	InitCallback func()
}

type EntityOptions struct {
	DrawPriority int
	Tags         []Tag
	InitCallback func()
}

type Tag struct {
	Name string
}

func NewEntity(s *Stage, x, y, w, h int, r rune, fg termbox.Attribute, bg termbox.Attribute, cells []*TermBoxCell, collidesPhysically bool, options EntityOptions) *Entity {
	drawPriority, tags, initCallback := options.DrawPriority, options.Tags, options.InitCallback
	p := Point{x, y}
	cell := &TermBoxCell{&termbox.Cell{r, fg, bg}, collidesPhysically, TileMapCellData{}}
	return &Entity{s, p, w, h, r, cell, cells, drawPriority, tags, initCallback}
}

func (e *Entity) SetStage(s *Stage) {
	e.Stage = s
}

func (e *Entity) GetStage() *Stage {
	return e.Stage
}

func (e *Entity) SetCells(s *Stage) {
	newPositionY := e.Position.y

	for i := 0; i < e.Height; i++ {
		newPositionX := e.Position.x
		if i != 0 {
			newPositionY += 1
		}

		for j := 0; j < e.Width; j++ {
			if j != 0 {
				newPositionX += 1
			}

			if e.Cells != nil {
				index := j

				tileMapCell := e.Cells[index]
				if len(e.Cells) > index {
					s.Canvas.OverWriteCanvasCell(newPositionX, newPositionY, tileMapCell)
				}
			} else {
				tileMapCell := e.Cell
				s.Canvas.OverWriteCanvasCell(newPositionX, newPositionY, tileMapCell)
			}
		}
	}
}

func (e *Entity) GetCells() []*TermBoxCell {
	return e.Cells
}

func (e *Entity) Update(s *Stage, event termbox.Event, time time.Duration) {
}

func (e *Entity) SetPosition(x, y int) {
	e.SetPositionX(x)
	e.SetPositionY(y)
}

func (e *Entity) SetPositionX(x int) {
	e.Position.x = x
}

func (e *Entity) SetPositionY(y int) {
	e.Position.y = y
}

func (e *Entity) GetPositionX() int {
	return e.Position.x
}

func (e *Entity) GetPositionY() int {
	return e.Position.y
}

func (e *Entity) GetPosition() (int, int) {
	return e.Position.x, e.Position.y
}

func (e *Entity) GetScreenOffset() (int, int) {
	screenWidth, screenHeight := e.Stage.Game.getScreenSize()
	return (screenWidth - e.Width) / 2, (screenHeight - e.Height) / 2
}

func (e *Entity) Destroy() {
}

func (e *Entity) GetDrawPriority() int {
	return e.DrawPriority
}

func (e *Entity) GetTags() []Tag {
	return e.Tags
}

func (e *Entity) IsInsideOfCanvasBoundaries() bool {
	return e.GetStage().Canvas.IsInsideOfBoundaries(e.GetPositionX(), e.GetPositionY())
}


================================================
FILE: game.go
================================================
package vimman

import (
	"time"

	"github.com/nsf/termbox-go"
	"github.com/pkg/errors"
)

const (
	bgColor = termbox.ColorBlack
	fgColor = termbox.ColorWhite
)

type Point struct {
	x int
	y int
}

type Game struct {
	Stage        *Stage
	screenSizeX  int
	screenSizeY  int
	VimManEvents chan VimManEvent
}

type GameOptions struct {
	fps          float64
	initialLevel int
	bgCell       *termbox.Cell
	VimManEvents chan VimManEvent
}

type VimManEvent struct {
	Content string
}

func NewGame(opts GameOptions) *Game {
	bgCell := &termbox.Cell{'░', fgColor, bgColor}
	game := &Game{nil, 0, 0, opts.VimManEvents}
	stage := NewStage(game, opts.initialLevel, opts.fps, bgCell)
	game.Stage = stage
	return game
}

type Renderer interface {
	Update(*Stage, termbox.Event, time.Duration)
	Destroy()
	SetCells(*Stage)
	GetCells() []*TermBoxCell
	GetPosition() (int, int)
	GetPositionX() int
	GetPositionY() int
	SetPositionX(int)
	GetScreenOffset() (int, int)
	GetDrawPriority() int
	GetTags() []Tag
	ShouldCenterHorizontally() bool
}

// main game loop
// handles events, updates and renders stage and entities
func gameLoop(termboxEvents chan termbox.Event, vimManEvents chan VimManEvent, game *Game) {
	termbox.Clear(fgColor, bgColor)
	game.setScreenSize(termbox.Size())
	stage := game.Stage
	stage.Init()
	stage.Render()
	lastUpdateTime := time.Now()

	for {
		termbox.Clear(fgColor, bgColor)
		update := time.Now()

		select {
		case event := <-termboxEvents:
			switch {
			case event.Key == termbox.KeyCtrlC:
				// exit on ctrc + c
				return
			case event.Type == termbox.EventResize:
				game.setScreenSize(termbox.Size())
			default:
				stage.update(event, update.Sub(lastUpdateTime))
			}
		default:
			stage.update(termbox.Event{}, update.Sub(lastUpdateTime))
		}

		// handle vim-man events here
		select {
		case event := <-vimManEvents:
			switch {
			case event.Content == "exit":
				return
			}
		default:

		}

		lastUpdateTime = time.Now()

		stage.Render()
		time.Sleep(time.Duration((update.Sub(time.Now()).Seconds()*1000.0)+1000.0/stage.Fps) * time.Millisecond)
	}
}

func termboxEventLoop(e chan termbox.Event) {
	for {
		e <- termbox.PollEvent()
	}
}

func exit(events chan termbox.Event) {
	close(events)
	termbox.Close()
}

func Init(level int) {
	if err := termbox.Init(); err != nil {
		panic(errors.Wrap(err, "failed to init termbox"))
	}

	if level == 0 {
		level = 1
	}

	termbox.SetOutputMode(termbox.Output256)
	termbox.Clear(termbox.ColorDefault, bgColor)

	termboxEvents := make(chan termbox.Event)
	go termboxEventLoop(termboxEvents)

	vimManEvents := make(chan VimManEvent)

	game := NewGame(GameOptions{
		fps:          50,
		initialLevel: level,
		VimManEvents: vimManEvents,
	})

	// main game loop, this is blocking
	gameLoop(termboxEvents, vimManEvents, game)

	// dump logs after the gameLoop stops
	if len(lg.logs) > 0 {
		lg.DumpLogs()
		time.Sleep(2 * time.Second)
	}

	exit(termboxEvents)
}

func (g *Game) setScreenSize(x, y int) {
	if x > 0 {
		g.screenSizeX = x
	}

	if y > 0 {
		g.screenSizeY = y
	}
}

func (g *Game) getScreenSize() (int, int) {
	return g.getScreenSizeX(), g.getScreenSizeY()
}

func (g *Game) getScreenSizeX() int {
	return g.screenSizeX
}

func (g *Game) getScreenSizeY() int {
	return g.screenSizeY
}


================================================
FILE: go.mod
================================================
module github.com/ozankasikci/vim-man

go 1.13

require (
	github.com/mattn/go-runewidth v0.0.8 // indirect
	github.com/nsf/termbox-go v0.0.0-20191229070316-58d4fcbce2a7
	github.com/pkg/errors v0.9.1
	github.com/stretchr/testify v1.4.0
)


================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/nsf/termbox-go v0.0.0-20191229070316-58d4fcbce2a7 h1:OkWEy7aQeQTbgdrcGi9bifx+Y6bMM7ae7y42hDFaBvA=
github.com/nsf/termbox-go v0.0.0-20191229070316-58d4fcbce2a7/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=


================================================
FILE: level.go
================================================
package vimman

import (
	"github.com/nsf/termbox-go"
	"reflect"
	"time"
)

type VimMode int

const (
	normalMode VimMode = iota
	insertMode
	colonMode
)

func (m VimMode) String() string {
	return [...]string{"NORMAL", "INSERT", "NORMAL"}[m]
}

const (
	levelTitleCoordX       int               = 0
	levelTitleCoordY       int               = 1
	levelTitleFg           termbox.Attribute = termbox.ColorGreen
	levelTitleBg           termbox.Attribute = termbox.ColorBlack
	levelExplanationCoordX int               = 0
	levelExplanationCoordY int               = 2
	levelHintCoordX        int               = 0
	levelHintCoordY        int               = 3
	typedCharacterFg       termbox.Attribute = termbox.ColorWhite
	typedCharacterBg       termbox.Attribute = termbox.ColorBlack
)

type Level struct {
	Game                 *Game
	VimMode              VimMode
	TileMapString        string
	TileMap              [][]*TermBoxCell
	TileData             TileMapCellDataMap
	Entities             []Renderer
	InputRunes           []rune
	BlockedKeys          []termbox.Key
	InputBlocked         bool
	TextShiftingDisabled bool
	BgCell               *termbox.Cell
	Width                int
	Height               int
	Init                 func()
	ColonLineCallbacks   map[string]func(game *Game)
}

func (l *Level) Update(s *Stage, t time.Duration) {

}

func (l *Level) SetCells(s *Stage) {

}

func (l *Level) GetSize() (int, int) {
	index, length := 0, 0

	for i, line := range l.TileMap {
		if len(line) > length {
			index, length = i, len(line)
		}
	}

	return len(l.TileMap[index]), len(l.TileMap)
}

func (l *Level) GetScreenOffset() (int, int) {
	offsetX, offsetY := 0, 0
	screenWidth, screenHeight := l.Game.getScreenSize()
	levelWidth, levelHeight := l.GetSize()

	if screenWidth > levelWidth {
		offsetX = (screenWidth - levelWidth) / 2
	}

	if screenHeight > levelHeight {
		offsetY = (screenHeight - levelHeight) / 2
	}

	return offsetX, offsetY
}

func (l *Level) LoadTileMapCells(parsedRunes [][]rune) [][]*TermBoxCell {
	var cells [][]*TermBoxCell

	for i, line := range parsedRunes {
		rowCells := make([]*TermBoxCell, len(line))
		var data TileMapCellData

		for j, char := range line {
			if _, ok := l.TileData[char]; !ok {
				if _, ok := CommonTileMapCellData[char]; !ok {
					data = NewTileMapCell(char, func() {}, i)
				} else {
					data = CommonTileMapCellData[char]
					data.LineNumber = i
				}
			} else {
				data = l.TileData[char]
			}

			if reflect.DeepEqual(data, TileMapCellData{}) {
				data = CommonTileMapCellData[char]
			}

			cell := &TermBoxCell{
				&termbox.Cell{data.Ch, data.FgColor, data.BgColor},
				data.CollidesPhysically,
				data,
			}
			rowCells[j] = cell
		}

		cells = append(cells, rowCells)
	}

	l.TileMap = cells
	return l.TileMap
}

func (l *Level) LoadTileMap() {
	parsed := ParseTileMapString(l.TileMapString)
	l.LoadTileMapCells(parsed)
}

// row, length
func (l *Level) GetTileMapDimensions() (int, int) {
	parsed := ParseTileMapString(l.TileMapString)
	rowLength := len(parsed[0])
	columnLength := len(parsed)
	return rowLength, columnLength
}

func (l *Level) InitDefaults() {
	// set default quit functions
	exitTerms := []string{"q", "quit", "exit"}

	if l.ColonLineCallbacks == nil {
		l.ColonLineCallbacks = make(map[string]func(*Game))
	}

	for _, term := range exitTerms {

		if _, ok := l.ColonLineCallbacks[term]; !ok {
			l.ColonLineCallbacks[term] = func(g *Game) {
				go func() {
					g.VimManEvents <- VimManEvent{Content: "exit"}
				}()
			}
		}
	}
}


================================================
FILE: level1_basic_movement.go
================================================
package vimman

import "github.com/nsf/termbox-go"

const LevelBasicMovementTileMapString = `
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  Try and find the exit |        |        |     |     |     |
+  +--+--+--+--+--+  +--+  +--+  +  +  +  +  +  +  +  +--+  +
|           |     |     |     |  |  |  |     |  |  |  |     |
+--+--+  +--+  +  +--+  +--+  +  +--+  +  +--+  +  +  +  +  +
|        |     |     |     |  |     |  |     |     |     |  |
+  +--+--+  +  +--+--+--+  +--+--+  +  +--+--+--+--+--+--+  +
|  |     |  |  |           |        |           |     |     |
+  +  +  +  +--+  +--+--+--+  +--+--+--+--+--+  +  +  +  +--+
|     |  |  |     |     |     |     |        |     |  |     |
+--+--+  +  +  +--+  +  +  +  +--+  +  +--+  +--+--+  +--+  +
|     |  |     |     |     |        |  |  |  |     |  |     |
+  +  +  +--+  +  +--+  +--+  +--+--+  +  +  +  +--+  +  +--+
|  |  |     |  |  |     |  |  |        |  |        |  |     |
+  +  +--+  +  +  +--+--+  +  +  +--+--+  +--+--+  +  +--+--+
|  |        |  |     |     |           |  |     |  |        |
+  +--+--+--+--+--+  +  +  +--+--+--+  +  +  +  +  +--+--+  +
|  |                 |  |  |     |     |     |  |           |
+  +--+  +--+  +--+--+  +  +  +  +  +--+--+  +  +--+--+--+--+
|        |     |        |     |              |         exit ↓ 
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
`

func NewLevelBasicMovement(g *Game) *Level {
	// create user
	user := NewUser(g.Stage, 1, 1)
	var entities []Renderer
	entities = append(entities, user)

	tileData := TileMapCellDataMap{
		'↓': TileMapCellData{
			Ch:                 '↓',
			FgColor:            termbox.ColorGreen,
			BgColor:            termbox.ColorBlack,
			CollidesPhysically: false,
			CollisionCallback: func() {
				levelInstance := NewLevelExitingVim(g)
				g.Stage.SetLevel(levelInstance)
			},
		},
	}

	level := &Level{
		Game:          g,
		Entities:      entities,
		TileMapString: LevelBasicMovementTileMapString,
		TileData:      tileData,
		InputBlocked:  true,
		VimMode:       normalMode,
		Init: func() {
			// load info
			titleOptions := WordOptions{
				InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			title := NewWord(g.Stage, levelTitleCoordX, levelTitleCoordY, "Level 1 - MOVING THE CURSOR", titleOptions)

			explanationOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			explanation := NewWord(g.Stage, levelExplanationCoordX, levelExplanationCoordY, "J: down, H: left, K: up, L: right", explanationOptions)

			g.Stage.AddScreenEntity(title, explanation)
		},
	}

	level.InitDefaults()
	return level
}


================================================
FILE: level2_exiting_vim.go
================================================
package vimman

import "github.com/nsf/termbox-go"

const LevelExitingVimTileMapString = `
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                            VIM                           |
|~                  Hardest editor to exit                  |
|~                            :)                            |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
|~                                                          |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
`

func NewLevelExitingVim(g *Game) *Level {
	// create user
	user := NewUser(g.Stage, 1, 1)
	var entities []Renderer
	entities = append(entities, user)

	tileData := TileMapCellDataMap{
		'↓': TileMapCellData{
			Ch:                 '↓',
			FgColor:            termbox.ColorGreen,
			BgColor:            termbox.ColorBlack,
			CollidesPhysically: false,
			CollisionCallback:  func() {},
		},
	}

	level := &Level{
		Game:          g,
		Entities:      entities,
		TileMapString: LevelExitingVimTileMapString,
		TileData:      tileData,
		InputBlocked:  true,
		VimMode:       normalMode,
		Init: func() {
			titleOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			title := NewWord(g.Stage, levelTitleCoordX, levelTitleCoordY, "Level 2 - EXITING VIM", titleOptions)

			explanationOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			explanation := NewWord(g.Stage, levelExplanationCoordX, levelExplanationCoordY, "You can't be a great Vim user without knowing how to exit.", explanationOptions)

			hintOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			hint := NewWord(g.Stage, levelHintCoordX, levelHintCoordY, "Type colon ':', then 'q', press enter", hintOptions)

			g.Stage.AddScreenEntity(title, explanation, hint)
		},
		ColonLineCallbacks: make(map[string]func(*Game)),
	}

	exitTerms := []string{"q", "quit", "exit"}
	for _, term := range exitTerms {
		if _, ok := level.ColonLineCallbacks[term]; !ok {
			level.ColonLineCallbacks[term] = func(g *Game) {
				levelInstance := NewLevelTextEditing(g)
				g.Stage.SetLevel(levelInstance)
			}
		}
	}

	level.InitDefaults()
	return level
}


================================================
FILE: level3_text_editing.go
================================================
package vimman

import "github.com/nsf/termbox-go"

const LevelTextEditingTileMapString = `
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|~                                                          |
|~                                                          |
|~                                                          |
|~  1- DELETION - Press "x" to delete the character under   |
|~              the cursor  in normal mode.                 |
|~                                                          |
|~               Yyou shalll noot paass                     |
|~                                                          |
|~  2- INSERTION - Move the cursor after the character      |
|~  where the text should be inserted, press i and type     |
|~                                                          |
|~                   Yu shll nt pss                         |
|~                                                          |
|~  3- APPENDING - Move the curser before the character     |
|~  where the text should be appended, press a and type     |
|~                                                          |
|~                    Yo shal no pas                        |
|~                                                          |
|~                                                          |
|~                                                          |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
`

func NewLevelTextEditing(g *Game) *Level {
	user := NewUser(g.Stage, 1, 1)
	var entities []Renderer
	entities = append(entities, user)

	tileData := TileMapCellDataMap{
		'↓': TileMapCellData{
			Ch:                 '↓',
			FgColor:            termbox.ColorGreen,
			BgColor:            termbox.ColorBlack,
			CollidesPhysically: false,
			CollisionCallback:  func() {},
		},
	}

	level := &Level{
		Game:          g,
		Entities:      entities,
		TileMapString: LevelTextEditingTileMapString,
		TileData:      tileData,
		VimMode:       normalMode,
		Init: func() {
			titleOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			title := NewWord(g.Stage, levelTitleCoordX, levelTitleCoordY, "Level 3 - TEXT EDITING", titleOptions)

			explanationOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			explanation := NewWord(g.Stage, levelExplanationCoordX, levelExplanationCoordY, "Delete, insert, append.", explanationOptions)

			hintOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			hint := NewWord(g.Stage, levelHintCoordX, levelHintCoordY, "Complete the 3 steps below to proceed to the next level.", hintOptions)

			g.Stage.AddScreenEntity(title, explanation, hint)
		},
	}

	level.InitDefaults()
	return level
}


================================================
FILE: level4_bomberman.go
================================================
package vimman

import (
	"github.com/nsf/termbox-go"
	"time"
)

const LevelBombermanTileMapString = `
▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅
█      ☵☲     ☵☲    █
█☲◼◼ ◼◼ ◼◼ ◼◼ ◼◼ ◼◼ █
█   ☲☵☲☵            █
█ ◼◼☲◼◼ ◼◼ ◼◼ ◼◼ ◼◼ █
█    ☲☵      ☵☲☵    █
█ ◼◼ ◼◼ ◼◼ ◼◼ ◼◼ ◼◼☲█
█☲☵      ☲☵   ☲☵  ☲☵█
█ ◼◼☵◼◼ ◼◼ ◼◼ ◼◼ ◼◼☵█
█           ☲☵ exit ↓
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
`

func NewLevelBomberman(g *Game) *Level {

	user := NewUser(g.Stage, 1, 1)
	var entities []Renderer
	entities = append(entities, user)
	tileData := TileMapCellDataMap{
		'b': TileMapCellData{
			Ch:                 '💣',
			FgColor:            termbox.ColorGreen,
			BgColor:            termbox.ColorBlack,
			CollidesPhysically: true,
			CollisionCallback:  nil,
			InitCallback: func(selfEntity *Entity) {
				bombOptions := WordOptions{InitCallback: nil, Fg: typedCharacterFg, Bg: typedCharacterBg, CollidesPhysically: true}
				bomb := NewWord(g.Stage, selfEntity.GetPositionX(), selfEntity.GetPositionY(), string('💣'), bombOptions)
				g.Stage.AddTypedEntity(bomb)

				go func() {
					<-time.After(1 * time.Second)
					posX := selfEntity.Position.x
					posY := selfEntity.Position.y
					positions := [][2]int{
						{posX, posY},
						{posX + 1, posY},
						{posX, posY + 1},
						{posX - 1, posY},
						{posX, posY - 1},
					}

					var positionsToBeCleared [][2]int

					for _, pos := range positions {
						if !g.Stage.Canvas.IsInsideOfBoundaries(pos[0], pos[1]) {
							return
						}

						// deliberately using reverse order in two dimensional array :/
						if !ContainsRune([]rune{'◼', '▅', '█'}, g.Stage.LevelInstance.TileMap[pos[1]][pos[0]].Ch) {
							positionsToBeCleared = append(positionsToBeCleared, [2]int{pos[0], pos[1]})
						}
					}

					// clear character and collision
					g.Stage.ClearTileMapCellsAt(positionsToBeCleared)
				}()
			},
		},
		'↓': TileMapCellData{
			Ch:                 '↓',
			FgColor:            termbox.ColorGreen,
			BgColor:            termbox.ColorBlack,
			CollidesPhysically: false,
			CollisionCallback: func() {
				levelInstance := NewLevelExitingVim(g)
				g.Stage.SetLevel(levelInstance)
			},
		},
	}

	level := &Level{
		Game:                 g,
		Entities:             entities,
		TileMapString:        LevelBombermanTileMapString,
		TileData:             tileData,
		InputRunes:           []rune{'b'},
		BlockedKeys:          []termbox.Key{termbox.KeyBackspace, termbox.KeyDelete},
		VimMode:              normalMode,
		TextShiftingDisabled: true,
		Init: func() {
			titleOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			title := NewWord(g.Stage, levelTitleCoordX, levelTitleCoordY, "Level 4 - VIMBERMAN", titleOptions)

			explanationOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			explanation := NewWord(g.Stage, levelExplanationCoordX, levelExplanationCoordY, "i: Insert Mode, esc: Back to Normal Mode", explanationOptions)

			hintOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: true}
			hint := NewWord(g.Stage, levelHintCoordX, levelHintCoordY, "Type b in Insert Mode to drop a bomb!", hintOptions)

			g.Stage.AddScreenEntity(title, explanation, hint)
		},
	}

	level.InitDefaults()
	return level
}


================================================
FILE: logger.go
================================================
package vimman

import (
	"fmt"
	"log"
	"os"
	"sync"
)

type Logger struct {
	logs []string
}

var instance *Logger
var once sync.Once

var lg = GetLogger()

func GetLogger() *Logger {
	once.Do(func() {
		instance = &Logger{
			logs: make([]string, 0),
		}
	})
	return instance
}

func (l *Logger) Log(strings ...string) {
	l.logs = append(l.logs, strings...)
}

func (l *Logger) LogValue(values ...interface{}) {
	var strings []string
	for _, string := range values {
		value := fmt.Sprintf("%v", string)
		strings = append(strings, value)
	}
	l.logs = append(l.logs, strings...)
}

func (l *Logger) DumpLogs() {
	for _, log := range l.logs {
		fmt.Println(log)
	}
}

func (l *Logger) WriteFile(text string) {
	if os.Getenv("DEBUG") == "1" {
		f, err := os.OpenFile("logfile.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("error opening file: %v", err)
		}
		defer f.Close()

		log.SetOutput(f)
		log.Println(text)
	}
}


================================================
FILE: stage.go
================================================
package vimman

import (
	"fmt"
	"github.com/nsf/termbox-go"
	"time"
)

var levelConstructors = []func(*Game) *Level{
	NewLevelBasicMovement,
	NewLevelExitingVim,
	NewLevelTextEditing,
	NewLevelBomberman,
}

// holds the current level meta data, canvas and entities
type Stage struct {
	Game           *Game
	Level          int
	LevelInstance  *Level
	Fps            float64
	CanvasEntities []Renderer // entities to be rendered in canvas
	ScreenEntities []Renderer // entities to be rendered outside of the canvas
	TypedEntities  []Renderer
	BgCell         *termbox.Cell
	Canvas         Canvas
	Width          int
	Height         int
	pixelMode      bool
	offsetx        int
	offsety        int
	ModeLabel      *Word
	ColonLine      *Word
}

func NewStage(g *Game, level int, fps float64, bgCell *termbox.Cell) *Stage {
	return &Stage{
		Game:           g,
		Level:          level,
		Fps:            fps,
		CanvasEntities: nil,
		ScreenEntities: nil,
		TypedEntities:  nil,
		BgCell:         bgCell,
		Canvas:         nil,
		Width:          0,
		Height:         0,
		pixelMode:      false,
		offsetx:        0,
		offsety:        0,
	}
}

func (s *Stage) AddCanvasEntity(e ...Renderer) {
	s.CanvasEntities = append(s.CanvasEntities, e...)
}

func (s *Stage) AddScreenEntity(e ...Renderer) {
	s.ScreenEntities = append(s.ScreenEntities, e...)
}

func (s *Stage) AddTypedEntity(e ...Renderer) {
	s.TypedEntities = append(s.TypedEntities, e...)
}

func (s *Stage) ClearCanvasEntities() {
	s.CanvasEntities = nil
	s.ScreenEntities = nil
	s.TypedEntities = nil
}

func (s *Stage) SetGame(game *Game) {
	s.Game = game
}

// this function handles all the rendering
// sets and renders in order:
// tilemap cells: the background cells
// canvas entities: canvas entities, they have update methods that gets called on stage.update method
// screen cells: cells outside of the canvas such as level label and instructions
func (s *Stage) Render() {
	s.SetCanvasBackgroundCells()

	for i, _ := range s.CanvasEntities {
		e := s.CanvasEntities[i]
		// sets the cells for the entity, so that it overwrites the background cell(s)
		e.SetCells(s)
	}

	s.TermboxSetScreenCells()
	s.TermboxSetCanvasCells()
	s.TermboxSetTypedCells()
	s.TermboxSetCursorCell()
	termbox.Flush()
}

func (s *Stage) update(ev termbox.Event, delta time.Duration) {
	for _, e := range s.CanvasEntities {
		e.Update(s, ev, delta)
	}

	s.updateModeLabel()
	s.updateColonLine()
}

func (s *Stage) updateModeLabel() {
	s.ModeLabel.Content = fmt.Sprintf("-- %s MODE --", s.LevelInstance.VimMode)
	s.ModeLabel.Entity.Cells = ConvertStringToCells(s.ModeLabel.Content, s.ModeLabel.Fg, s.ModeLabel.Bg)
}

func (s *Stage) updateColonLine() {
	s.ColonLine.Entity.Cells = ConvertStringToCells(s.ColonLine.Content, s.ColonLine.Fg, s.ColonLine.Bg)
}

func (s *Stage) Init() {
	level := s.Level - 1
	if level < 0 || level > len(levelConstructors) {
		level = 0
	}

	s.SetLevel(levelConstructors[level](s.Game))
}

func (s *Stage) SetLevel(levelInstance *Level) {
	s.Reset()
	s.LevelInstance = levelInstance
	s.LevelInstance.Init()
	s.LevelInstance.LoadTileMap()
	s.Resize(s.LevelInstance.GetTileMapDimensions())

	for _, e := range s.LevelInstance.Entities {
		s.AddCanvasEntity(e)
	}

	modeLabelOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: false, Tags: []Tag{{"ModeLabel"}}}
	s.ModeLabel = NewWord(s, 0, s.Game.getScreenSizeY()-2, fmt.Sprintf("-- %s MODE --", levelInstance.VimMode), modeLabelOptions)

	colonLineOptions := WordOptions{InitCallback: nil, Fg: levelTitleFg, Bg: levelTitleBg, CenterHorizontally: false, Tags: []Tag{{"ColonLine"}}}
	s.ColonLine = NewWord(s, 0, s.Game.getScreenSizeY()-1, "", colonLineOptions)

	s.AddScreenEntity(s.ModeLabel, s.ColonLine)
}

func (s *Stage) Reset() {
	s.ClearCanvasEntities()
	s.Canvas = NewCanvas(10, 10)
}

func (s *Stage) Resize(w, h int) {
	s.Width = w
	s.Height = h

	if s.pixelMode {
		s.Height *= 2
	}

	if s.LevelInstance.Height != 0 && s.LevelInstance.Width != 0 {
		s.Width = s.LevelInstance.Width
		s.Height = s.LevelInstance.Height
	}

	c := NewCanvas(s.Width, s.Height)

	// Copy old data that fits
	for i := 0; i < MinInt(s.Height, len(s.Canvas)); i++ {
		for j := 0; j < MinInt(s.Width, len(s.Canvas)); j++ {
			c[i][j] = s.Canvas[i][j]
		}
	}
	s.Canvas = c
}

func (s *Stage) GetDefaultBgCell() *TermBoxCell {
	return &TermBoxCell{s.BgCell, false, TileMapCellData{}}
}

func (s *Stage) GetRendererEntityByTag(wantedTag Tag) Renderer {
	for _, ce := range s.CanvasEntities {
		for _, tag := range ce.GetTags() {
			if tag.Name == wantedTag.Name {
				return ce
			}
		}
	}

	return nil
}

// sets the background cells to be rendered, this gets rendered first in the render method
// so that other cells can be overwritten into the same location
func (s *Stage) SetCanvasBackgroundCells() {
	for i, row := range s.Canvas {
		for j, _ := range row {
			if s.LevelInstance.TileMap[i][j].Cell != nil {
				// insert tile map cell
				s.Canvas.SetCellAt(i, j, s.LevelInstance.TileMap[i][j])
			} else {
				//insert default bg cell
				s.Canvas.SetCellAt(i, j, s.GetDefaultBgCell())
			}
		}
	}
}

// calls termbox.setCell, sets the coordinates and the cell attributes
// this does the actual rendering of the characters, thanks to termbox-go <3
func (s *Stage) TermboxSetCell(x, y int, cell *TermBoxCell, offset bool) {
	if offset {
		offsetX, offsetY := s.LevelInstance.GetScreenOffset()
		x += offsetX
		y += offsetY
	}

	termbox.SetCell(x, y, cell.Ch,
		termbox.Attribute(cell.Fg),
		termbox.Attribute(cell.Bg))
}

// sets the cells inside the canvas, offset is being applied in order to keep the canvas in center
func (s *Stage) TermboxSetCanvasCells() {
	for i, row := range s.Canvas {
		for j, _ := range row {
			cell := row[j]
			// intentionally use j,i in reverse order
			s.TermboxSetCell(j, i, cell, true)
		}
	}
}

// sets the cells outside of the canvas, no offset is being applied
func (s *Stage) TermboxSetScreenCells() {
	for _, e := range s.ScreenEntities {
		for j, _ := range e.GetCells() {
			cell := e.GetCells()[j]
			x := e.GetPositionX()
			offsetX := 0

			if e.ShouldCenterHorizontally() {
				offsetX, _ = e.GetScreenOffset()
			}

			x = x + j + offsetX
			s.TermboxSetCell(x, e.GetPositionY(), cell, false)
		}
	}
}

func (s *Stage) TermboxSetTypedCells() {
	for _, e := range s.TypedEntities {
		for j, _ := range e.GetCells() {
			cell := e.GetCells()[j]
			s.TermboxSetCell(e.GetPositionX(), e.GetPositionY(), cell, true)
		}
	}
}

func (s *Stage) TermboxSetCursorCell() {
	cursorEntity := s.GetRendererEntityByTag(Tag{"Cursor"})
	for j, _ := range cursorEntity.GetCells() {
		cell := cursorEntity.GetCells()[j]
		s.TermboxSetCell(cursorEntity.GetPositionX(), cursorEntity.GetPositionY(), cell, true)
	}
}

func (s *Stage) CheckCollision(x, y int) bool {
	return s.Canvas.CheckCollision(x, y)
}

// clear Canvas cell and TileMap cell at the given positions
func (s *Stage) ClearTileMapCellsAt(positions [][2]int) {
	for _, pos := range positions {
		options := DefaultWordOptions()
		emptyChar := NewEmptyCharacter(s, pos[0], pos[1], options)
		s.AddTypedEntity(emptyChar)
		s.LevelInstance.TileMap[pos[1]][pos[0]].collidesPhysically = false
	}
}


================================================
FILE: termbox_cell.go
================================================
package vimman

import "github.com/nsf/termbox-go"

type TermBoxCell struct {
	*termbox.Cell
	collidesPhysically bool
	cellData           TileMapCellData
}

func EmptyTileMapCell() *TermBoxCell {
	data := CommonTileMapCellData[' ']
	cell := &TermBoxCell{
		&termbox.Cell{data.Ch, data.FgColor, data.BgColor},
		data.CollidesPhysically,
		data,
	}

	return cell
}


================================================
FILE: tilemap.go
================================================
package vimman

import (
	"github.com/nsf/termbox-go"
	"strings"
)

func NewTileMapCell(ch rune, fn func(), lineNumber int) TileMapCellData {
	return TileMapCellData{
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 ch,
		CollidesPhysically: false,
		CollisionCallback:  fn,
		LineNumber:         lineNumber,
	}
}

type TileMapCellData struct {
	Ch                 rune
	BgColor            termbox.Attribute
	FgColor            termbox.Attribute
	CollidesPhysically bool
	CollisionCallback  func()
	InitCallback       func(*Entity)
	LineNumber         int
}

type TileMapCellDataMap map[rune]TileMapCellData

var CommonTileMapCellData = TileMapCellDataMap{
	'0': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 ' ',
		CollidesPhysically: false,
		CollisionCallback:  func() {},
	},
	'↓': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '↓',
		CollidesPhysically: false,
		CollisionCallback:  func() {},
	},
	'+': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '+',
		CollidesPhysically: true,
		CollisionCallback:  func() {},
	},
	'-': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '-',
		CollidesPhysically: true,
		CollisionCallback:  func() {},
	},
	'|': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '|',
		CollidesPhysically: true,
		CollisionCallback:  func() {},
	},
	'█': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '█',
		CollidesPhysically: true,
		CollisionCallback:  func() {},
	},
	'◼': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '◼',
		CollidesPhysically: true,
		CollisionCallback:  func() {},
	},
	'▅': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '▅',
		CollidesPhysically: true,
		CollisionCallback:  func() {},
	},
	'▀': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '▀',
		CollidesPhysically: true,
		CollisionCallback:  func() {},
	},
	'☵': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '☵',
		CollidesPhysically: true,
		CollisionCallback:  func() {},
	},
	'☲': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 '☲',
		CollidesPhysically: true,
		CollisionCallback:  func() {},
	},
	' ': {
		BgColor:            termbox.ColorBlack,
		FgColor:            termbox.ColorWhite,
		Ch:                 ' ',
		CollidesPhysically: false,
		CollisionCallback:  func() {},
	},
}

func ParseLine(l string) []rune {
	var lineChars []rune

	//chars := strings.Split(l, " ")
	//line := strings.Join(chars, "")

	for _, char := range l {
		lineChars = append(lineChars, char)
	}

	return lineChars
}

func ParseTileMapString(tileMap string) [][]rune {
	var parsed [][]rune

	lines := strings.Split(tileMap, "\n")
	lines = lines[1 : len(lines)-1]

	for _, line := range lines {
		l := ParseLine(line)
		parsed = append(parsed, l)
	}

	return parsed
}


================================================
FILE: user.go
================================================
package vimman

import (
	"github.com/nsf/termbox-go"
	"reflect"
	"time"
)

type Class int

type User struct {
	*Entity
}

func (u *User) ShouldCenterHorizontally() bool {
	return false
}

func NewUser(s *Stage, x, y int) (u *User) {
	cells := []*TermBoxCell{
		{&termbox.Cell{'▒', termbox.ColorGreen, bgColor}, false, TileMapCellData{}},
	}

	tags := []Tag{{"Cursor"}}
	entityOptions := EntityOptions{9999, tags, nil}
	e := NewEntity(s, x, y, 1, 1, ' ', termbox.ColorBlue, termbox.ColorWhite, cells, false, entityOptions)
	u = &User{
		Entity: e,
	}
	return
}

func (u *User) handleNormalModeEvents(s *Stage, event termbox.Event) {
	switch event.Ch {
	case 'k':
		nextY := u.GetPositionY() - 1
		if !s.CheckCollision(u.GetPositionX(), nextY) {
			u.SetPositionY(nextY)
		}
	case 'j':
		nextY := u.GetPositionY() + 1
		if !s.CheckCollision(u.GetPositionX(), nextY) {
			u.SetPositionY(nextY)
		}
	case 'l':
		nextX := u.GetPositionX() + 1
		if !s.CheckCollision(nextX, u.GetPositionY()) {
			u.SetPositionX(nextX)
		}
	case 'h':
		nextX := u.GetPositionX() - 1
		if !s.CheckCollision(nextX, u.GetPositionY()) {
			u.SetPositionX(nextX)
		}
	case 'i':
		if s.LevelInstance.VimMode != insertMode && !s.LevelInstance.InputBlocked {
			s.LevelInstance.VimMode = insertMode
		}

	case 'x':
		if ContainsTermboxKey(s.LevelInstance.BlockedKeys, termbox.KeyDelete) {
			return
		}

		if s.LevelInstance.InputBlocked {
			return
		}

		x := u.GetPositionX()
		y := u.GetPositionY()

		// keep the last element in place, insert an empty cell before the last character in the line
		tileMap := s.LevelInstance.TileMap
		lastElement := s.LevelInstance.TileMap[y][len(s.LevelInstance.TileMap[y])-1]
		tileMap[y] = append(tileMap[y][:x], tileMap[y][x+1:len(tileMap[y])-1]...)
		tileMap[y] = append(tileMap[y], EmptyTileMapCell(), lastElement)

	case ':':
		if s.LevelInstance.VimMode == normalMode {
			s.LevelInstance.VimMode = colonMode
			s.ColonLine.Content = ":"
		}
	}
}

func (u *User) handleInsertModeEvents(s *Stage, event termbox.Event) {
	// return on empty event
	if reflect.DeepEqual(event, termbox.Event{}) {
		return
	}

	switch event.Key {
	// switch to normal mode on esc key event
	case termbox.KeyEsc:
		s.LevelInstance.VimMode = normalMode
		return
	case termbox.KeyBackspace, termbox.KeyBackspace2:
		if ContainsTermboxKey(s.LevelInstance.BlockedKeys, termbox.KeyBackspace) ||
			ContainsTermboxKey(s.LevelInstance.BlockedKeys, termbox.KeyBackspace2) {
			return
		}

		characterOptions := WordOptions{InitCallback: nil, Fg: typedCharacterFg, Bg: typedCharacterBg}
		character := NewEmptyCharacter(s, u.GetPositionX()-1, u.GetPositionY(), characterOptions)
		if !character.IsInsideOfCanvasBoundaries() {
			return
		}

		s.AddTypedEntity(character)
		u.SetPositionX(u.GetPositionX() - 1)
	default:
		// don't allow non-character events
		if event.Ch == 0 {
			return
		}

		// check if rune input is allowed
		if len(s.LevelInstance.InputRunes) > 0 {
			if !ContainsRune(s.LevelInstance.InputRunes, event.Ch) {
				return
			}
		}

		characterOptions := WordOptions{InitCallback: nil, Fg: typedCharacterFg, Bg: typedCharacterBg}
		character := NewWord(s, u.GetPositionX(), u.GetPositionY(), string(event.Ch), characterOptions)
		if !character.IsInsideOfCanvasBoundaries() {
			return
		}

		if !s.LevelInstance.TextShiftingDisabled {
			x := u.GetPositionX()
			y := u.GetPositionY()
			tileMap := s.LevelInstance.TileMap[y]
			lastElement := tileMap[len(tileMap)-1]
			tileMap = append(tileMap[:x], append([]*TermBoxCell{character.Cell}, tileMap[x:len(tileMap)-2]...)...)
			tileMap = append(tileMap, lastElement)
		}

		// type a character and add as typed entity
		s.AddTypedEntity(character)
		u.SetPositionX(u.GetPositionX() + 1)

		// at the end of the current line, move the cursor next line
		if s.Canvas.IsInLastColumn(u.GetPositionX()) {
			u.SetPosition(1, u.GetPositionY()+1)
		}

		if character.InitCallback != nil {
			character.InitCallback()
		}

		if s.LevelInstance.TileData[event.Ch].InitCallback != nil {
			s.LevelInstance.TileData[event.Ch].InitCallback(character.Entity)
		}
	}
}

func (u *User) handleColonModeEvents(s *Stage, event termbox.Event) {
	if event.Key == termbox.KeyEnter {
		s.LevelInstance.VimMode = normalMode

		if len(s.LevelInstance.ColonLineCallbacks) > 0 {
			if fn, ok := s.LevelInstance.ColonLineCallbacks[s.ColonLine.Content[1:]]; ok {
				fn(s.Game)
			}
		}
	}

	if event.Ch != 0 {
		s.ColonLine.Content = s.ColonLine.Content + string(event.Ch)
	}
}

func (u *User) Update(s *Stage, event termbox.Event, delta time.Duration) {
	switch s.LevelInstance.VimMode {
	case colonMode:
		u.handleColonModeEvents(s, event)
	case normalMode:
		u.handleNormalModeEvents(s, event)
	case insertMode:
		u.handleInsertModeEvents(s, event)
	}
}


================================================
FILE: utils.go
================================================
package vimman

import "github.com/nsf/termbox-go"

func ContainsRune(s []rune, e rune) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

func ContainsString(s []string, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

func ContainsTermboxKey(s []termbox.Key, e termbox.Key) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

func MinInt(a, b int) int {
	if a < b {
		return a
	}
	return b
}


================================================
FILE: word.go
================================================
package vimman

import (
	"github.com/nsf/termbox-go"
	"time"
)

type Word struct {
	*Entity
	Content            string
	Speed              float64
	Direction          Direction
	CenterHorizontally bool
	Fg                 termbox.Attribute
	Bg                 termbox.Attribute
}

func (w *Word) ShouldCenterHorizontally() bool {
	return w.CenterHorizontally
}

type WordOptions struct {
	InitCallback       func()
	Fg                 termbox.Attribute
	Bg                 termbox.Attribute
	CollidesPhysically bool
	CenterHorizontally bool
	Tags               []Tag
}

func ConvertStringToCells(s string, fg termbox.Attribute, bg termbox.Attribute) []*TermBoxCell {
	var arr []*TermBoxCell

	for i := 0; i < len([]rune(s)); i++ {
		cell := &TermBoxCell{
			Cell: &termbox.Cell{
				[]rune(s)[i],
				fg,
				bg,
			},
			collidesPhysically: false,
			cellData:           TileMapCellData{}}

		arr = append(arr, cell)
	}

	return arr
}

func NewWord(s *Stage, x, y int, content string, options WordOptions) *Word {
	fg, bg, collidesPhysically, centerHorizontally := options.Fg, options.Bg, options.CollidesPhysically, options.CenterHorizontally

	cells := ConvertStringToCells(content, fg, bg)
	entityOptions := EntityOptions{2000, options.Tags, nil}
	e := NewEntity(s, x, y, len(content), 1, ' ', fg, bg, cells, collidesPhysically, entityOptions)
	return &Word{
		Entity:             e,
		Content:            content,
		Direction:          horizontal,
		CenterHorizontally: centerHorizontally,
		Fg:                 fg,
		Bg:                 bg,
	}
}

func DefaultWordOptions() WordOptions {
	return WordOptions{InitCallback: nil, Fg: typedCharacterFg, Bg: typedCharacterBg, CollidesPhysically: false, CenterHorizontally: true}
}

func NewEmptyCharacter(s *Stage, x, y int, options WordOptions) *Word {
	return NewWord(s, x, y, string(" "), options)
}

func (w *Word) Update(s *Stage, event termbox.Event, delta time.Duration) {
}
Download .txt
gitextract_a3ra5gw2/

├── .gitignore
├── LICENSE
├── README.md
├── canvas.go
├── canvas_test.go
├── cmd/
│   └── console/
│       └── vimman.go
├── docs/
│   └── LEVEL_PLANNING.md
├── entity.go
├── game.go
├── go.mod
├── go.sum
├── level.go
├── level1_basic_movement.go
├── level2_exiting_vim.go
├── level3_text_editing.go
├── level4_bomberman.go
├── logger.go
├── stage.go
├── termbox_cell.go
├── tilemap.go
├── user.go
├── utils.go
└── word.go
Download .txt
SYMBOL INDEX (139 symbols across 17 files)

FILE: canvas.go
  type Canvas (line 7) | type Canvas
    method GetCellAt (line 17) | func (c Canvas) GetCellAt(x, y int) *TermBoxCell {
    method CheckCollision (line 21) | func (c Canvas) CheckCollision(x, y int) bool {
    method OverWriteCanvasCell (line 42) | func (c *Canvas) OverWriteCanvasCell(x, y int, termboxCell *TermBoxCel...
    method SetCellAt (line 49) | func (c *Canvas) SetCellAt(row, column int, cell *TermBoxCell) {
    method IsInsideOfBoundaries (line 53) | func (c *Canvas) IsInsideOfBoundaries(x, y int) bool {
    method IsInLastColumn (line 57) | func (c *Canvas) IsInLastColumn(x int) bool {
  function NewCanvas (line 9) | func NewCanvas(width, height int) Canvas {

FILE: canvas_test.go
  function TestCanvasCheckCollision (line 9) | func TestCanvasCheckCollision(t *testing.T) {

FILE: cmd/console/vimman.go
  function main (line 9) | func main() {

FILE: entity.go
  type Direction (line 8) | type Direction
  constant horizontal (line 11) | horizontal Direction = iota
  constant vertical (line 12) | vertical
  type Entity (line 15) | type Entity struct
    method SetStage (line 45) | func (e *Entity) SetStage(s *Stage) {
    method GetStage (line 49) | func (e *Entity) GetStage() *Stage {
    method SetCells (line 53) | func (e *Entity) SetCells(s *Stage) {
    method GetCells (line 82) | func (e *Entity) GetCells() []*TermBoxCell {
    method Update (line 86) | func (e *Entity) Update(s *Stage, event termbox.Event, time time.Durat...
    method SetPosition (line 89) | func (e *Entity) SetPosition(x, y int) {
    method SetPositionX (line 94) | func (e *Entity) SetPositionX(x int) {
    method SetPositionY (line 98) | func (e *Entity) SetPositionY(y int) {
    method GetPositionX (line 102) | func (e *Entity) GetPositionX() int {
    method GetPositionY (line 106) | func (e *Entity) GetPositionY() int {
    method GetPosition (line 110) | func (e *Entity) GetPosition() (int, int) {
    method GetScreenOffset (line 114) | func (e *Entity) GetScreenOffset() (int, int) {
    method Destroy (line 119) | func (e *Entity) Destroy() {
    method GetDrawPriority (line 122) | func (e *Entity) GetDrawPriority() int {
    method GetTags (line 126) | func (e *Entity) GetTags() []Tag {
    method IsInsideOfCanvasBoundaries (line 130) | func (e *Entity) IsInsideOfCanvasBoundaries() bool {
  type EntityOptions (line 28) | type EntityOptions struct
  type Tag (line 34) | type Tag struct
  function NewEntity (line 38) | func NewEntity(s *Stage, x, y, w, h int, r rune, fg termbox.Attribute, b...

FILE: game.go
  constant bgColor (line 11) | bgColor = termbox.ColorBlack
  constant fgColor (line 12) | fgColor = termbox.ColorWhite
  type Point (line 15) | type Point struct
  type Game (line 20) | type Game struct
    method setScreenSize (line 154) | func (g *Game) setScreenSize(x, y int) {
    method getScreenSize (line 164) | func (g *Game) getScreenSize() (int, int) {
    method getScreenSizeX (line 168) | func (g *Game) getScreenSizeX() int {
    method getScreenSizeY (line 172) | func (g *Game) getScreenSizeY() int {
  type GameOptions (line 27) | type GameOptions struct
  type VimManEvent (line 34) | type VimManEvent struct
  function NewGame (line 38) | func NewGame(opts GameOptions) *Game {
  type Renderer (line 46) | type Renderer interface
  function gameLoop (line 63) | func gameLoop(termboxEvents chan termbox.Event, vimManEvents chan VimMan...
  function termboxEventLoop (line 108) | func termboxEventLoop(e chan termbox.Event) {
  function exit (line 114) | func exit(events chan termbox.Event) {
  function Init (line 119) | func Init(level int) {

FILE: level.go
  type VimMode (line 9) | type VimMode
    method String (line 17) | func (m VimMode) String() string {
  constant normalMode (line 12) | normalMode VimMode = iota
  constant insertMode (line 13) | insertMode
  constant colonMode (line 14) | colonMode
  constant levelTitleCoordX (line 22) | levelTitleCoordX       int               = 0
  constant levelTitleCoordY (line 23) | levelTitleCoordY       int               = 1
  constant levelTitleFg (line 24) | levelTitleFg           termbox.Attribute = termbox.ColorGreen
  constant levelTitleBg (line 25) | levelTitleBg           termbox.Attribute = termbox.ColorBlack
  constant levelExplanationCoordX (line 26) | levelExplanationCoordX int               = 0
  constant levelExplanationCoordY (line 27) | levelExplanationCoordY int               = 2
  constant levelHintCoordX (line 28) | levelHintCoordX        int               = 0
  constant levelHintCoordY (line 29) | levelHintCoordY        int               = 3
  constant typedCharacterFg (line 30) | typedCharacterFg       termbox.Attribute = termbox.ColorWhite
  constant typedCharacterBg (line 31) | typedCharacterBg       termbox.Attribute = termbox.ColorBlack
  type Level (line 34) | type Level struct
    method Update (line 52) | func (l *Level) Update(s *Stage, t time.Duration) {
    method SetCells (line 56) | func (l *Level) SetCells(s *Stage) {
    method GetSize (line 60) | func (l *Level) GetSize() (int, int) {
    method GetScreenOffset (line 72) | func (l *Level) GetScreenOffset() (int, int) {
    method LoadTileMapCells (line 88) | func (l *Level) LoadTileMapCells(parsedRunes [][]rune) [][]*TermBoxCell {
    method LoadTileMap (line 126) | func (l *Level) LoadTileMap() {
    method GetTileMapDimensions (line 132) | func (l *Level) GetTileMapDimensions() (int, int) {
    method InitDefaults (line 139) | func (l *Level) InitDefaults() {

FILE: level1_basic_movement.go
  constant LevelBasicMovementTileMapString (line 5) | LevelBasicMovementTileMapString = `
  function NewLevelBasicMovement (line 29) | func NewLevelBasicMovement(g *Game) *Level {

FILE: level2_exiting_vim.go
  constant LevelExitingVimTileMapString (line 5) | LevelExitingVimTileMapString = `
  function NewLevelExitingVim (line 30) | func NewLevelExitingVim(g *Game) *Level {

FILE: level3_text_editing.go
  constant LevelTextEditingTileMapString (line 5) | LevelTextEditingTileMapString = `
  function NewLevelTextEditing (line 30) | func NewLevelTextEditing(g *Game) *Level {

FILE: level4_bomberman.go
  constant LevelBombermanTileMapString (line 8) | LevelBombermanTileMapString = `
  function NewLevelBomberman (line 22) | func NewLevelBomberman(g *Game) *Level {

FILE: logger.go
  type Logger (line 10) | type Logger struct
    method Log (line 28) | func (l *Logger) Log(strings ...string) {
    method LogValue (line 32) | func (l *Logger) LogValue(values ...interface{}) {
    method DumpLogs (line 41) | func (l *Logger) DumpLogs() {
    method WriteFile (line 47) | func (l *Logger) WriteFile(text string) {
  function GetLogger (line 19) | func GetLogger() *Logger {

FILE: stage.go
  type Stage (line 17) | type Stage struct
    method AddCanvasEntity (line 54) | func (s *Stage) AddCanvasEntity(e ...Renderer) {
    method AddScreenEntity (line 58) | func (s *Stage) AddScreenEntity(e ...Renderer) {
    method AddTypedEntity (line 62) | func (s *Stage) AddTypedEntity(e ...Renderer) {
    method ClearCanvasEntities (line 66) | func (s *Stage) ClearCanvasEntities() {
    method SetGame (line 72) | func (s *Stage) SetGame(game *Game) {
    method Render (line 81) | func (s *Stage) Render() {
    method update (line 97) | func (s *Stage) update(ev termbox.Event, delta time.Duration) {
    method updateModeLabel (line 106) | func (s *Stage) updateModeLabel() {
    method updateColonLine (line 111) | func (s *Stage) updateColonLine() {
    method Init (line 115) | func (s *Stage) Init() {
    method SetLevel (line 124) | func (s *Stage) SetLevel(levelInstance *Level) {
    method Reset (line 144) | func (s *Stage) Reset() {
    method Resize (line 149) | func (s *Stage) Resize(w, h int) {
    method GetDefaultBgCell (line 173) | func (s *Stage) GetDefaultBgCell() *TermBoxCell {
    method GetRendererEntityByTag (line 177) | func (s *Stage) GetRendererEntityByTag(wantedTag Tag) Renderer {
    method SetCanvasBackgroundCells (line 191) | func (s *Stage) SetCanvasBackgroundCells() {
    method TermboxSetCell (line 207) | func (s *Stage) TermboxSetCell(x, y int, cell *TermBoxCell, offset boo...
    method TermboxSetCanvasCells (line 220) | func (s *Stage) TermboxSetCanvasCells() {
    method TermboxSetScreenCells (line 231) | func (s *Stage) TermboxSetScreenCells() {
    method TermboxSetTypedCells (line 248) | func (s *Stage) TermboxSetTypedCells() {
    method TermboxSetCursorCell (line 257) | func (s *Stage) TermboxSetCursorCell() {
    method CheckCollision (line 265) | func (s *Stage) CheckCollision(x, y int) bool {
    method ClearTileMapCellsAt (line 270) | func (s *Stage) ClearTileMapCellsAt(positions [][2]int) {
  function NewStage (line 36) | func NewStage(g *Game, level int, fps float64, bgCell *termbox.Cell) *St...

FILE: termbox_cell.go
  type TermBoxCell (line 5) | type TermBoxCell struct
  function EmptyTileMapCell (line 11) | func EmptyTileMapCell() *TermBoxCell {

FILE: tilemap.go
  function NewTileMapCell (line 8) | func NewTileMapCell(ch rune, fn func(), lineNumber int) TileMapCellData {
  type TileMapCellData (line 19) | type TileMapCellData struct
  type TileMapCellDataMap (line 29) | type TileMapCellDataMap
  function ParseLine (line 118) | func ParseLine(l string) []rune {
  function ParseTileMapString (line 131) | func ParseTileMapString(tileMap string) [][]rune {

FILE: user.go
  type Class (line 9) | type Class
  type User (line 11) | type User struct
    method ShouldCenterHorizontally (line 15) | func (u *User) ShouldCenterHorizontally() bool {
    method handleNormalModeEvents (line 33) | func (u *User) handleNormalModeEvents(s *Stage, event termbox.Event) {
    method handleInsertModeEvents (line 86) | func (u *User) handleInsertModeEvents(s *Stage, event termbox.Event) {
    method handleColonModeEvents (line 158) | func (u *User) handleColonModeEvents(s *Stage, event termbox.Event) {
    method Update (line 174) | func (u *User) Update(s *Stage, event termbox.Event, delta time.Durati...
  function NewUser (line 19) | func NewUser(s *Stage, x, y int) (u *User) {

FILE: utils.go
  function ContainsRune (line 5) | func ContainsRune(s []rune, e rune) bool {
  function ContainsString (line 14) | func ContainsString(s []string, e string) bool {
  function ContainsTermboxKey (line 23) | func ContainsTermboxKey(s []termbox.Key, e termbox.Key) bool {
  function MinInt (line 32) | func MinInt(a, b int) int {

FILE: word.go
  type Word (line 8) | type Word struct
    method ShouldCenterHorizontally (line 18) | func (w *Word) ShouldCenterHorizontally() bool {
    method Update (line 74) | func (w *Word) Update(s *Stage, event termbox.Event, delta time.Durati...
  type WordOptions (line 22) | type WordOptions struct
  function ConvertStringToCells (line 31) | func ConvertStringToCells(s string, fg termbox.Attribute, bg termbox.Att...
  function NewWord (line 50) | func NewWord(s *Stage, x, y int, content string, options WordOptions) *W...
  function DefaultWordOptions (line 66) | func DefaultWordOptions() WordOptions {
  function NewEmptyCharacter (line 70) | func NewEmptyCharacter(s *Stage, x, y int, options WordOptions) *Word {
Condensed preview — 23 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (57K chars).
[
  {
    "path": ".gitignore",
    "chars": 308,
    "preview": ".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*.t"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2018 Ozan Kaşıkçı\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 1340,
    "preview": "# VimMan\n\nLearn how to use Vim in its natural environment, the Terminal!\n## About\n\nVimMan is terminal program that's a s"
  },
  {
    "path": "canvas.go",
    "chars": 1207,
    "preview": "package vimman\n\nimport \"os\"\n\n//import tb \"github.com/nsf/termbox-go\"\n\ntype Canvas [][]*TermBoxCell\n\nfunc NewCanvas(width"
  },
  {
    "path": "canvas_test.go",
    "chars": 631,
    "preview": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n)\n\nfunc TestCanva"
  },
  {
    "path": "cmd/console/vimman.go",
    "chars": 236,
    "preview": "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"
  },
  {
    "path": "docs/LEVEL_PLANNING.md",
    "chars": 4761,
    "preview": "# Vim-Man Level Planning\n\n## Level 1: Basic Movement\n\n*   **Vim Concepts:** `h` (left), `j` (down), `k` (up), `l` (right"
  },
  {
    "path": "entity.go",
    "chars": 2638,
    "preview": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"time\"\n)\n\ntype Direction int\n\nconst (\n\thorizontal Direction = iot"
  },
  {
    "path": "game.go",
    "chars": 3271,
    "preview": "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.Co"
  },
  {
    "path": "go.mod",
    "chars": 238,
    "preview": "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/"
  },
  {
    "path": "go.sum",
    "chars": 1531,
    "preview": "github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.m"
  },
  {
    "path": "level.go",
    "chars": 3533,
    "preview": "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 VimMod"
  },
  {
    "path": "level1_basic_movement.go",
    "chars": 2688,
    "preview": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\nconst LevelBasicMovementTileMapString = `\n+--+--+--+--+--+--+--+--+-"
  },
  {
    "path": "level2_exiting_vim.go",
    "chars": 3217,
    "preview": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\nconst LevelExitingVimTileMapString = `\n+--+--+--+--+--+--+--+--+--+-"
  },
  {
    "path": "level3_text_editing.go",
    "chars": 2839,
    "preview": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\nconst LevelTextEditingTileMapString = `\n+--+--+--+--+--+--+--+--+--+"
  },
  {
    "path": "level4_bomberman.go",
    "chars": 3301,
    "preview": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"time\"\n)\n\nconst LevelBombermanTileMapString = `\n▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅"
  },
  {
    "path": "logger.go",
    "chars": 953,
    "preview": "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 "
  },
  {
    "path": "stage.go",
    "chars": 7237,
    "preview": "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\tN"
  },
  {
    "path": "termbox_cell.go",
    "chars": 363,
    "preview": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\ntype TermBoxCell struct {\n\t*termbox.Cell\n\tcollidesPhysically bool\n\tc"
  },
  {
    "path": "tilemap.go",
    "chars": 3393,
    "preview": "package vimman\n\nimport (\n\t\"github.com/nsf/termbox-go\"\n\t\"strings\"\n)\n\nfunc NewTileMapCell(ch rune, fn func(), lineNumber i"
  },
  {
    "path": "user.go",
    "chars": 4785,
    "preview": "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"
  },
  {
    "path": "utils.go",
    "chars": 501,
    "preview": "package vimman\n\nimport \"github.com/nsf/termbox-go\"\n\nfunc ContainsRune(s []rune, e rune) bool {\n\tfor _, a := range s {\n\t\t"
  },
  {
    "path": "word.go",
    "chars": 1933,
    "preview": "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"
  }
]

About this extraction

This page contains the full source code of the ozankasikci/vim-man GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 23 files (50.8 KB), approximately 15.9k tokens, and a symbol index with 139 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!