{"id":13412951,"url":"https://github.com/JoelOtter/termloop","last_synced_at":"2025-03-14T19:30:42.986Z","repository":{"id":32551323,"uuid":"36133658","full_name":"JoelOtter/termloop","owner":"JoelOtter","description":"Terminal-based game engine for Go, built on top of Termbox","archived":false,"fork":false,"pushed_at":"2024-07-29T23:57:28.000Z","size":901,"stargazers_count":1424,"open_issues_count":6,"forks_count":83,"subscribers_count":32,"default_branch":"master","last_synced_at":"2024-09-23T06:07:20.378Z","etag":null,"topics":["game-engine","golang","terminal","terminal-game"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JoelOtter.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-05-23T17:12:34.000Z","updated_at":"2024-09-18T15:51:42.000Z","dependencies_parsed_at":"2022-07-27T23:17:22.608Z","dependency_job_id":null,"html_url":"https://github.com/JoelOtter/termloop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelOtter%2Ftermloop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelOtter%2Ftermloop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelOtter%2Ftermloop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelOtter%2Ftermloop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JoelOtter","download_url":"https://codeload.github.com/JoelOtter/termloop/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221495317,"owners_count":16832458,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["game-engine","golang","terminal","terminal-game"],"created_at":"2024-07-30T20:01:31.516Z","updated_at":"2024-10-26T04:31:28.698Z","avatar_url":"https://github.com/JoelOtter.png","language":"Go","funding_links":[],"categories":["Go","游戏开发","Game Development","遊戲開發","Relational Databases","\u003cspan id=\"游戏开发-game-development\"\u003e游戏开发 Game Development\u003c/span\u003e"],"sub_categories":["检索及分析资料库","Advanced Console UIs","Search and Analytic Databases","高級控制台界面","SQL 查询语句构建库","高级控制台界面","\u003cspan id=\"高级控制台用户界面-advanced-console-uis\"\u003e高级控制台用户界面 Advanced Console UIs\u003c/span\u003e"],"readme":"## Termloop\n\n[![Join the chat at https://gitter.im/JoelOtter/termloop](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/JoelOtter/termloop?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge) [![GoDoc](https://godoc.org/github.com/JoelOtter/termloop?status.svg)](http://godoc.org/github.com/JoelOtter/termloop)\n\n![](_examples/images/maze.png)\n\nTermloop is a pure Go game engine for the terminal, built on top of the excellent [Termbox](https://github.com/nsf/termbox-go). It provides a simple render loop for building games in the terminal, and is focused on making terminal game development as easy and as fun as possible.\n\nTermloop is still under active development so changes may be breaking. I add any breaking changes to the [Changelog](https://github.com/JoelOtter/termloop/wiki/Changelog) - hopefully at this stage there shouldn't be too many. Pull requests and issues are *very* welcome, and do feel free to ask any questions you might have on the Gitter. I hope you enjoy using Termloop; I've had a blast making it.\n\n## Installing\nInstall and update with `go get -u github.com/JoelOtter/termloop`\n\n## Features\n\n- Keyboard and mouse input\n- Collision detection\n- Render timers\n- Level offsets to simulate 'camera' movement\n- Debug logging\n- Built-in entity types such as:\n - Framerate counters\n - Rectangles\n - Text\n- Loading entities from ASCII art\n- Loading colour maps from images\n- Loading level maps from JSON\n- Optional 'pixel mode' - draw two 'pixels' to a terminal character, doubling screen height at the expense of being able to render text.\n- Pure Go - easy portability of compiled games, and cross-compilation built right in.\n\n*To see what's on the roadmap, have a look at the [issue tracker](https://github.com/JoelOtter/termloop/issues).*\n\n## termloop/extra\n\nThe Termloop extras are a collection of types and functions, the use of which will not result in a fully portable binary - that is, they have some external dependencies. However, if you're willing to require these dependencies in your project, they should integrate quite nicely with the rest of Termloop. Some of the included examples use these extras.\n\n- Audio playback \n - audio.go\n - Requirements: PortAudio and libsndfile\n\n## Cool stuff built with Termloop\n \n- [Included examples](https://github.com/JoelOtter/termloop/tree/master/_examples) (@JoelOtter)\n- [Number Crusher](https://github.com/aquilax/number_crusher) (@aquilax)\n- [Go Tapper](https://github.com/swapagarwal/gotapper) (@swapagarwal)\n- [Frame Assault](https://github.com/Ariemeth/frame_assault) (@Ariemeth)\n- [Minesweeper](https://github.com/ryanbaer/minesweeper-go) (@ryanbaer)\n- [Termtank](https://github.com/TerrySolar/termtank) (@TerrySolar)\n- [Snake](https://github.com/mattkelly/snake-go) (@mattkelly)\n- [Go Man's Sky](https://rawktron.itch.io/go-mans-sky) (@rawktron)\n- [conwaygo](https://github.com/buckley-w-david/conwaygo) (@buckley-w-david)\n- [Doric (a Columns clone)](https://github.com/svera/doric) (@svera)\n- [Terminal-based Snake](https://github.com/tristangoossens/snake-go) (@tristangoossens)\n- [Sokoban](https://github.com/tristangoossens/sokoban-go) (@tristangoossens)\n- [Gopher Typer](https://github.com/ScottBrooks/gopher_typer) (@scottbrooksca)\n- [Tetris](https://bitbucket.org/cam73/tetris) (@cam73)\n- [Gorched](https://github.com/zladovan/gorched) (@zladovan)\n- [Go Invaders](https://github.com/afagundes/go-invaders) (@afagundes)\n\n_Feel free to add yours with a pull request!_\n\n## Tutorial\n \n\u003e More full documentation will be added to the Wiki soon. In the meantime, check out this tutorial, the [GoDoc](http://godoc.org/github.com/JoelOtter/termloop), or the [included examples](https://github.com/JoelOtter/termloop/tree/master/_examples). If you get stuck during this tutorial, worry not, the full source is [here](https://github.com/JoelOtter/termloop/blob/master/_examples/tutorial.go).\n\nCreating a blank Termloop game is as simple as:\n\n```go\npackage main\n\nimport tl \"github.com/JoelOtter/termloop\"\n\nfunc main() {\n\tgame := tl.NewGame()\n\tgame.Start()\n}\n```\n\nWe can press Ctrl+C to exit. It's just a blank screen - let's make it a little more interesting.\n\nLet's make a green background, because grass is really nice to run around on. We create a new level like so:\n\n```go\nlevel := tl.NewBaseLevel(tl.Cell{\n\tBg: tl.ColorGreen,\n\tFg: tl.ColorBlack,\n\tCh: 'v',\n})\n```\n\nCell is a struct that represents one cell on the terminal. We can set its background and foreground colours, and the character that is displayed. Creating a [BaseLevel](http://godoc.org/github.com/JoelOtter/termloop#BaseLevel) in this way will fill the level with this Cell.\n\nLet's make a nice pretty lake, too. We'll use a [Rectangle](http://godoc.org/github.com/JoelOtter/termloop#Rectangle) for this. We'll put the lake at position (10, 10), with width 50 and height 20. All measurements are in terminal characters! The last argument is the colour of the Rectangle.\n\n```go\nlevel.AddEntity(tl.NewRectangle(10, 10, 50, 20, tl.ColorBlue))\n```\n\nWe don't need to use a Level - we can add entities directly to the [Screen](http://godoc.org/github.com/JoelOtter/termloop#Screen)! This is great for building a HUD, or a very simple app. However, if we want camera scrolling or collision detection, we're going to need to use a Level.\n\nPutting together what we have so far:\n\n```go\npackage main\n\nimport tl \"github.com/JoelOtter/termloop\"\n\nfunc main() {\n\tgame := tl.NewGame()\n\tlevel := tl.NewBaseLevel(tl.Cell{\n\t\tBg: tl.ColorGreen,\n\t\tFg: tl.ColorBlack,\n\t\tCh: 'v',\n\t})\n\tlevel.AddEntity(tl.NewRectangle(10, 10, 50, 20, tl.ColorBlue))\n\tgame.Screen().SetLevel(level)\n\tgame.Start()\n}\n```\n\nWhen we run it with `go run tutorial.go`, it looks like this:\n\n![](_examples/images/tutorial01.png)\n\nPretty! Ish. OK, let's create a character that can walk around the environment. We're going to use object composition here - we'll create a new struct type, which extends an [Entity](http://godoc.org/github.com/JoelOtter/termloop#Entity).\n\nTo have Termloop draw our new type, we need to implement the [Drawable](http://godoc.org/github.com/JoelOtter/termloop#Drawable) interface, which means we need two methods: **Draw()** and **Tick()**. The Draw method defines how our type is drawn to the [Screen](http://godoc.org/github.com/JoelOtter/termloop#Screen) (Termloop's internal drawing surface), and the Tick method defines how we handle input.\n\nWe don't need to do anything special for `Draw`, and it's already handled by `Entity`, so we just need a `Tick`:\n\n```go\ntype Player struct {\n\t*tl.Entity\n}\n\nfunc (player *Player) Tick(event tl.Event) {\n\tif event.Type == tl.EventKey { // Is it a keyboard event?\n\t\tx, y := player.Position()\n\t\tswitch event.Key { // If so, switch on the pressed key.\n\t\tcase tl.KeyArrowRight:\n\t\t\tplayer.SetPosition(x+1, y)\n\t\tcase tl.KeyArrowLeft:\n\t\t\tplayer.SetPosition(x-1, y)\n\t\tcase tl.KeyArrowUp:\n\t\t\tplayer.SetPosition(x, y-1)\n\t\tcase tl.KeyArrowDown:\n\t\t\tplayer.SetPosition(x, y+1)\n\t\t}\n\t}\n}\n```\n\nNow that we've built our Player type, let's add one to the level. I'm going to use the character '옷', because I think it looks a bit like a stick man.\n\n```go\nplayer := Player{tl.NewEntity(1, 1, 1, 1)}\n// Set the character at position (0, 0) on the entity.\nplayer.SetCell(0, 0, \u0026tl.Cell{Fg: tl.ColorRed, Ch: '옷'})\nlevel.AddEntity(\u0026player)\n\n```\n\n![](_examples/images/tutorial02.png)\n\nRunning the game again, we see that we can now move around the map using the arrow keys. Neato! However, we can stroll across the lake just as easily as the grass. Our character isn't the Messiah, ~~he's a very naughty boy,~~ so let's add some collisions.\n\nIn Termloop, we have two interfaces that are used for collisions. Here they are.\n\n```go\n// Physical represents something that can collide with another\n// Physical, but cannot process its own collisions.\n// Optional addition to Drawable.\ntype Physical interface {\n\tPosition() (int, int) // Return position, x and y\n\tSize() (int, int)     // Return width and height\n}\n\n// DynamicPhysical represents something that can process its own collisions.\n// Implementing this is an optional addition to Drawable.\ntype DynamicPhysical interface {\n\tPosition() (int, int) // Return position, x and y\n\tSize() (int, int)     // Return width and height\n\tCollide(Physical)     // Handle collisions with another Physical\n}\n```\n\nIt's pretty simple - if we want our object to be 'solid', then we implement Physical. If we want a solid object that actually does some processing on its own collisions, we implement DynamicPhysical! Essentially this just involves adding one more method to your type.\n\nNote that, for performance reasons, you should try and have as few DynamicPhysicals as possible - for example, our Player will be one, but the lake need only be a Physical.\n\nThe Rectangle type already implements Physical, so we don't actually need to do anything. As well, Player already implements DynamicPhysical because of the embedded Entity. However, we want custom behaviour for Collide, so let's implement that method. For that, we'll have to modify our struct and Tick method, to keep track of the Player's previous position so we can move it back there if it collides with something.\n\n\n\n```go\ntype Player struct {\n\t*tl.Entity\n\tprevX  int\n\tprevY  int\n}\n\nfunc (player *Player) Tick(event tl.Event) {\n\tif event.Type == tl.EventKey { // Is it a keyboard event?\n\t\tplayer.prevX, player.prevY = player.Position()\n\t\tswitch event.Key { // If so, switch on the pressed key.\n\t\tcase tl.KeyArrowRight:\n\t\t\tplayer.SetPosition(player.prevX+1, player.prevY)\n\t\tcase tl.KeyArrowLeft:\n\t\t\tplayer.SetPosition(player.prevX-1, player.prevY)\n\t\tcase tl.KeyArrowUp:\n\t\t\tplayer.SetPosition(player.prevX, player.prevY-1)\n\t\tcase tl.KeyArrowDown:\n\t\t\tplayer.SetPosition(player.prevX, player.prevY+1)\n\t\t}\n\t}\n}\n\nfunc (player *Player) Collide(collision tl.Physical) {\n\t// Check if it's a Rectangle we're colliding with\n\tif _, ok := collision.(*tl.Rectangle); ok {\n\t\tplayer.SetPosition(player.prevX, player.prevY)\n\t}\n}\n\n```\n\nNot too much extra code! We can now see that the Player can't walk out into the lake. If you see the Player overlap the lake slightly on one side, that's likely because the 'stick man' character we used isn't quite standard width.\n\nWe've now got something that looks a bit like a very simple exploration game. There's one more thing to add - let's have the camera scroll to keep the Player in the centre of the screen!\n\nThere isn't really a 'camera' in Termloop, like you might find in another graphics library. Instead, we set an offset, and the Screen draws our level appropriately. In our case it's really simple - all we need is for the Player to have a pointer to the Level, so we can make calls on it. Then we simply modify our Draw method, like so:\n\n```go\ntype Player struct {\n\t*tl.Entity\n\tprevX  int\n\tprevY  int\n\tlevel  *tl.BaseLevel\n}\n\nfunc (player *Player) Draw(screen *tl.Screen) {\n\tscreenWidth, screenHeight := screen.Size()\n\tx, y := player.Position()\n\tplayer.level.SetOffset(screenWidth/2-x, screenHeight/2-y)\n  // We need to make sure and call Draw on the underlying Entity.\n\tplayer.Entity.Draw(screen)\n}\n\n\n// in func main\nplayer := Player{\n\tEntity:   tl.NewEntity(1, 1, 1, 1),\n\tlevel: level,\n}\n```\n\nThat's all it takes. We should now see the camera moving. Of course, due to the static, repeating background, this doesn't look terribly convincing - it kind of looks like the player is standing still and everything else is moving! We could remedy this by, for example, only updating the offset when the player is closer to the edge of the screen. I'll leave it up to you as a challenge.\n\n![](_examples/images/tutorial03.png)\n\nWe've now reached the end of our tutorial - I hope it's been useful! If you'd like to learn a little more about Termloop, more comprehensive documentation is coming on the Wiki. In the meantime, you can check out the [GoDoc](http://godoc.org/github.com/JoelOtter/termloop), or the [included examples](https://github.com/JoelOtter/termloop/tree/master/_examples). I'll be hanging out on the [Gitter](https://gitter.im/JoelOtter/termloop) too, if you have any questions. Have fun, and please do show me if you make something cool!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJoelOtter%2Ftermloop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FJoelOtter%2Ftermloop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FJoelOtter%2Ftermloop/lists"}