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

### Level - 2 - How to exit Vim

### Level - 3 - Basic text editing

### Level - 4 - Vimberman!

## 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) {
}
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
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.