├── .github └── workflows │ └── release.yml ├── .gitignore ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── makefile └── pong ├── main.go ├── object.go ├── object_test.go └── pong.go /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - "v[0-9]+.[0-9]+.[0-9]+" 6 | 7 | jobs: 8 | goreleaser: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - 12 | name: Checkout 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - 17 | name: Set up Go 18 | uses: actions/setup-go@v3 19 | - 20 | name: Run GoReleaser 21 | uses: goreleaser/goreleaser-action@v3 22 | with: 23 | distribution: goreleaser 24 | version: latest 25 | args: release --rm-dist 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | pong/pong 3 | bin 4 | _obj 5 | .vscode 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - main: ./pong 3 | binary: pong 4 | env: 5 | - CGO_ENABLED=0 6 | goos: 7 | - linux 8 | - windows 9 | - darwin 10 | archives: 11 | - format: zip 12 | replacements: 13 | amd64: x86_64 14 | checksum: 15 | name_template: 'checksums.txt' 16 | snapshot: 17 | name_template: "{{ incpatch .Version }}-next" 18 | changelog: 19 | sort: asc 20 | filters: 21 | exclude: 22 | - '^docs:' 23 | - '^test:' 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 kurehajime 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pong-command 2 | 3 | ![pong](https://cloud.githubusercontent.com/assets/4569916/7273449/e6c410be-e92e-11e4-89dd-ba6903089706.gif) 4 | 5 | Pong-command is a CLI game. 6 | 7 | **PO**ng is **N**ot pin**G**. 8 | 9 | ## How to use. 10 | 11 | ### 1. Download Win/Mac OSX/Linux binary 12 | 13 | [Download here. Windows,MacOSX,Linux,BSD binary](https://github.com/kurehajime/pong-command/releases) 14 | 15 | ### 2. Move file or Add directory to PATH 16 | 17 | ***Mac OSX / Linux :*** 18 | 19 | Copy a file into /usr/local/bin 20 | 21 | ``` 22 | cp ./pong /usr/local/bin/pong 23 | ``` 24 | 25 | ***Windows :*** 26 | 27 | [Add directory to PATH Environment Variable](http://www.nextofwindows.com/how-to-addedit-environment-variables-in-windows-7/) 28 | 29 | ### 3. pong 30 | 31 | Run command. 32 | 33 | ```sh 34 | $ ./pong [IP ADDRESS] [TARGET] 35 | ``` 36 | Target is the score to stop the game at. If not provided, the game runs forever. 37 | 38 | Start game. 39 | 40 | ``` 41 | 42 | 006 - 000 43 | -------------------------------------------------------------------------------- 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 1 53 | 9 || 54 | 2 || 55 | || . || 56 | || 1 || 57 | || 6 58 | || 8 59 | . 60 | 1 61 | . 62 | 1 63 | 64 | 65 | -------------------------------------------------------------------------------- 66 | EXIT : ESC KEY 67 | 68 | 69 | ``` 70 | 71 | ## ... or install by 'go install' 72 | 73 | ```sh 74 | 75 | go install github.com/kurehajime/pong-command/pong@latest 76 | 77 | ``` 78 | 79 | ## LICENSE 80 | 81 | This software is released under the MIT License, see LICENSE.txt. 82 | 83 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kurehajime/pong-command 2 | 3 | go 1.19 4 | 5 | require github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e 6 | 7 | require github.com/mattn/go-runewidth v0.0.4 // indirect 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= 2 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 3 | github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= 4 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 5 | github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY= 6 | github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= 7 | github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e h1:Vbib8wJAaMEF9jusI/kMSYMr/LtRzM7+F9MJgt/nH8k= 8 | github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= 9 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | out: 2 | go get github.com/mitchellh/gox 3 | rm -rf ./_obj 4 | gox -output "_obj/{{.OS}}_{{.Arch}}/{{.Dir}}" ./... 5 | mv ./_obj/darwin_386 ./_obj/macos_386 6 | mv ./_obj/darwin_amd64 ./_obj/macos_amd64 7 | -find ./_obj \! -name "*.zip" -type d -exec zip -r -j {}.zip {} \; -exec rm -R -d {} \; -------------------------------------------------------------------------------- /pong/main.go: -------------------------------------------------------------------------------- 1 | // main.go 2 | package main 3 | 4 | import ( 5 | "os" 6 | "strconv" 7 | ) 8 | 9 | func main() { 10 | ipAddr := "*" 11 | target := 0 12 | if len(os.Args) >= 2 { 13 | ipAddr = os.Args[1] 14 | } 15 | if len(os.Args) >= 3 { 16 | if i, err := strconv.Atoi(os.Args[2]); err == nil { 17 | target = i 18 | } 19 | } 20 | start(ipAddr, target) 21 | } 22 | -------------------------------------------------------------------------------- /pong/object.go: -------------------------------------------------------------------------------- 1 | // object.go 2 | package main 3 | 4 | //Alignment ------------------------------------------------ 5 | type Alignment int 6 | 7 | const ( 8 | //VERTICAL , 9 | VERTICAL Alignment = iota 10 | //HORIZONAL , 11 | HORIZONAL 12 | ) 13 | 14 | //Objective interface------------------------------------------------ 15 | type Objective interface { 16 | Point() Point 17 | Size() Size 18 | Str() string 19 | } 20 | 21 | //Collisionable interface 22 | type Collisionable interface { 23 | Objective 24 | Collision(Collisionable) bool 25 | } 26 | 27 | //Movable interface 28 | type Movable interface { 29 | Objective 30 | Move(int, int) 31 | Next() 32 | Prev() 33 | Turn() 34 | } 35 | 36 | //CollisionableMovable interface 37 | type CollisionableMovable interface { 38 | Objective 39 | Collision(Collisionable) bool 40 | Move(int, int) 41 | Next() 42 | Prev() 43 | Turn() 44 | } 45 | 46 | //Point :Object point------------------------------------------------ 47 | type Point struct { 48 | X int 49 | Y int 50 | } 51 | 52 | //Size : Object size------------------------------------------------ 53 | type Size struct { 54 | Width int 55 | Height int 56 | } 57 | 58 | //Object : Object------------------------------------------------ 59 | type Object struct { 60 | point Point 61 | size Size 62 | str string 63 | } 64 | 65 | //NewObject : make new Object 66 | func NewObject(x, y, w, h int, s string) Object { 67 | return Object{ 68 | point: Point{X: x, Y: y}, 69 | size: Size{Width: w, Height: h}, 70 | str: s, 71 | } 72 | } 73 | 74 | //Point : get Point 75 | func (o Object) Point() Point { 76 | return o.point 77 | } 78 | 79 | //Size : get Size 80 | func (o Object) Size() Size { 81 | return o.size 82 | } 83 | 84 | //Str get Str 85 | func (o Object) Str() string { 86 | return o.str 87 | } 88 | 89 | //MovableObject is movable------------------------------------------------ 90 | type MovableObject struct { 91 | Object 92 | Arrow Point 93 | } 94 | 95 | //NewMovableObject : NewMovableObject 96 | func NewMovableObject(x, y, w, h int, s string, ax, ay int) MovableObject { 97 | return MovableObject{ 98 | Object: Object{ 99 | point: Point{X: x, Y: y}, 100 | size: Size{Width: w, Height: h}, 101 | str: s, 102 | }, 103 | Arrow: Point{X: ax, Y: ay}, 104 | } 105 | } 106 | 107 | //Move get Moved 108 | func (o *MovableObject) Move(addX, addY int) { 109 | o.point.X += addX 110 | o.point.Y += addY 111 | } 112 | 113 | //Next get Moved 114 | func (o *MovableObject) Next() { 115 | o.point.X += o.Arrow.X 116 | o.point.Y += o.Arrow.Y 117 | 118 | } 119 | 120 | //Prev get Move Cancel 121 | func (o *MovableObject) Prev() { 122 | o.point.X -= o.Arrow.X 123 | o.point.Y -= o.Arrow.Y 124 | } 125 | 126 | //Turn Turn 127 | func (o *MovableObject) Turn(a Alignment) { 128 | if a == VERTICAL { 129 | o.Arrow.X = -1 * o.Arrow.X 130 | } else { 131 | o.Arrow.Y = -1 * o.Arrow.Y 132 | } 133 | } 134 | 135 | //CollisionableObject is Collisionable------------------------------------------------ 136 | type CollisionableObject struct { 137 | Object 138 | } 139 | 140 | //NewCollisionableObject : get new NewCollisionableObject 141 | func NewCollisionableObject(x, y, w, h int, s string) CollisionableObject { 142 | return CollisionableObject{ 143 | Object{point: Point{X: x, Y: y}, 144 | size: Size{Width: w, Height: h}, 145 | str: s, 146 | }, 147 | } 148 | } 149 | 150 | //Collision : check Collision 151 | func (o CollisionableObject) Collision(o2 Collisionable) bool { 152 | points1 := []Point{ 153 | Point{X: o.Point().X, Y: o.Point().Y}, 154 | Point{X: o.Point().X + o.Size().Width - 1, Y: o.Point().Y + o.Size().Height - 1}, 155 | Point{X: o.Point().X + o.Size().Width - 1, Y: o.Point().Y}, 156 | Point{X: o.Point().X, Y: o.Point().Y + o.Size().Height - 1}, 157 | } 158 | points2 := []Point{ 159 | Point{X: o2.Point().X, Y: o2.Point().Y}, 160 | Point{X: o2.Point().X + o2.Size().Width - 1, Y: o2.Point().Y + o2.Size().Height - 1}, 161 | Point{X: o2.Point().X + o2.Size().Width - 1, Y: o2.Point().Y}, 162 | Point{X: o2.Point().X, Y: o2.Point().Y + o2.Size().Height - 1}, 163 | } 164 | for i := range points2 { 165 | if o.Point().X <= points2[i].X && o.Point().X+o.Size().Width-1 >= points2[i].X && 166 | o.Point().Y <= points2[i].Y && o.Point().Y+o.Size().Height-1 >= points2[i].Y { 167 | return true 168 | } 169 | } 170 | for i := range points1 { 171 | if o2.Point().X <= points1[i].X && o2.Point().X+o2.Size().Width-1 >= points1[i].X && 172 | o2.Point().Y <= points1[i].Y && o2.Point().Y+o2.Size().Height-1 >= points1[i].Y { 173 | return true 174 | } 175 | } 176 | return false 177 | } 178 | 179 | //CollisionableMovableObject : Collisionable+MovableObject 180 | type CollisionableMovableObject struct { 181 | MovableObject 182 | } 183 | 184 | //NewCollisionableMovableObject : NewCollisionableMovableObject 185 | func NewCollisionableMovableObject(x, y, w, h int, s string, ax, ay int) CollisionableMovableObject { 186 | return CollisionableMovableObject{ 187 | MovableObject: MovableObject{ 188 | Object: Object{ 189 | point: Point{X: x, Y: y}, 190 | size: Size{Width: w, Height: h}, 191 | str: s, 192 | }, 193 | Arrow: Point{X: ax, Y: ay}, 194 | }, 195 | } 196 | } 197 | 198 | //Collision : check Collision 199 | func (o CollisionableMovableObject) Collision(o2 Collisionable) bool { 200 | return NewCollisionableObject(o.Point().X, o.Point().Y, o.Size().Width, o.Size().Height, o.Str()).Collision(o2) 201 | } 202 | -------------------------------------------------------------------------------- /pong/object_test.go: -------------------------------------------------------------------------------- 1 | // object_test.go 2 | package main 3 | 4 | import "testing" 5 | 6 | func TestCollisionable(t *testing.T) { 7 | patterns := []struct { 8 | A Collisionable 9 | B Collisionable 10 | Result bool 11 | }{ 12 | {NewCollisionableObject(0, 0, 10, 10, "#"), NewCollisionableObject(30, 30, 30, 30, "#"), false}, 13 | {NewCollisionableObject(0, 0, 10, 10, "#"), NewCollisionableObject(5, 5, 10, 10, "#"), true}, 14 | {NewCollisionableObject(0, 0, 10, 10, "#"), NewCollisionableObject(5, 5, 1, 1, "#"), true}, 15 | {NewCollisionableObject(0, 0, 10, 10, "#"), NewCollisionableObject(5, 5, 0, 0, "#"), true}, 16 | {NewCollisionableObject(1, 1, 1, 1, "#"), NewCollisionableObject(1, 1, 1, 1, "#"), true}, 17 | {NewCollisionableObject(0, 0, 3, 3, "#"), NewCollisionableObject(4, 4, 2, 2, "#"), false}, 18 | } 19 | for i := range patterns { 20 | if patterns[i].A.Collision(patterns[i].B) != patterns[i].Result { 21 | t.Errorf("Falt Pattern %d", i) 22 | continue 23 | } 24 | if patterns[i].B.Collision(patterns[i].A) != patterns[i].Result { 25 | t.Errorf("Falt Pattern %d", i) 26 | continue 27 | } 28 | } 29 | } 30 | func TestMovable(t *testing.T) { 31 | o := NewMovableObject(0, 0, 1, 1, "*", 1, 1) 32 | o.Next() 33 | o.Next() 34 | o.Next() 35 | 36 | if o.Point().X != 3 && o.Point().Y != 3 { 37 | t.Errorf("3,3!=%d,%d", o.Point().X, o.Point().Y) 38 | } 39 | o.Turn(HORIZONAL) 40 | o.Next() 41 | 42 | if o.Point().X != 4 && o.Point().Y != 2 { 43 | t.Errorf("4,2!=%d,%d", o.Point().X, o.Point().Y) 44 | } 45 | o.Move(-4, -2) 46 | if o.Point().X != 0 && o.Point().Y != 0 { 47 | t.Errorf("0,0!=%d,%d", o.Point().X, o.Point().Y) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pong/pong.go: -------------------------------------------------------------------------------- 1 | // pong.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "math" 7 | "time" 8 | 9 | "math/rand" 10 | 11 | "github.com/nsf/termbox-go" 12 | ) 13 | 14 | type state struct { 15 | Player CollisionableMovableObject 16 | Enemy CollisionableMovableObject 17 | Ball CollisionableMovableObject 18 | Shadows []MovableObject 19 | TopLine CollisionableObject 20 | BottomLine CollisionableObject 21 | LeftLine CollisionableObject 22 | RightLine CollisionableObject 23 | ScorePlayer int 24 | ScoreEnemy int 25 | Count int 26 | Target int 27 | } 28 | 29 | var ( 30 | _temespan = 10 31 | _height = 25 32 | _width = 80 33 | ) 34 | 35 | //timer event 36 | func timerLoop(tch chan bool) { 37 | for { 38 | tch <- true 39 | time.Sleep(time.Duration(_temespan) * time.Millisecond) 40 | } 41 | } 42 | 43 | //key events 44 | func keyEventLoop(kch chan termbox.Key) { 45 | for { 46 | switch ev := termbox.PollEvent(); ev.Type { 47 | case termbox.EventKey: 48 | kch <- ev.Key 49 | default: 50 | } 51 | } 52 | } 53 | 54 | //draw console 55 | func update(s state) { 56 | termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) 57 | drawObj(s.Ball) 58 | for i := range s.Shadows { 59 | drawObj(s.Shadows[i]) 60 | } 61 | drawObj(s.LeftLine) 62 | drawObj(s.RightLine) 63 | drawObj(s.TopLine) 64 | drawObj(s.BottomLine) 65 | drawLine(1, 0, "EXIT : ESC KEY") 66 | drawLine(_width-10, 0, fmt.Sprintf("%03d - %03d", s.ScoreEnemy, s.ScorePlayer)) 67 | drawObj(s.Player) 68 | drawObj(s.Enemy) 69 | termbox.Flush() 70 | } 71 | 72 | //draw object 73 | func drawObj(o Objective) { 74 | for w := 0; w < o.Size().Width; w++ { 75 | for h := 0; h < o.Size().Height; h++ { 76 | termbox.SetCell(o.Point().X+w, o.Point().Y+h, 77 | []rune(o.Str())[0], termbox.ColorDefault, termbox.ColorDefault) 78 | } 79 | } 80 | } 81 | 82 | //drawLine 83 | func drawLine(x, y int, str string) { 84 | runes := []rune(str) 85 | for i := 0; i < len(runes); i++ { 86 | termbox.SetCell(x+i, y, runes[i], termbox.ColorDefault, termbox.ColorDefault) 87 | } 88 | } 89 | 90 | // controller 91 | func controller(s state, kch chan termbox.Key, tch chan bool) string { 92 | var ballMaxTime = 9 93 | var ballTime = ballMaxTime 94 | var enemyMaxTime = 7 95 | var enemyTime = enemyMaxTime 96 | for { 97 | select { 98 | case key := <-kch: //key event 99 | switch key { 100 | case termbox.KeyEsc, termbox.KeyCtrlC: //game end 101 | return "" 102 | case termbox.KeyArrowUp: 103 | s.Player.Move(0, -1) 104 | break 105 | case termbox.KeyArrowDown: 106 | s.Player.Move(0, 1) 107 | break 108 | } 109 | s = updateStatus(s) 110 | case <-tch: //time event 111 | ballTime = ballTime - 1 112 | if ballTime < 0 { 113 | ballTime = ballMaxTime - int(math.Min(float64(s.Count), 8)) 114 | s = ballMove(s) 115 | } 116 | enemyTime = enemyTime - 1 117 | if enemyTime < 0 { 118 | enemyTime = enemyMaxTime 119 | s.Enemy.Move(0, enemyMove(s.Enemy, s.Ball, s.Player)) 120 | s = updateStatus(s) 121 | } 122 | break 123 | default: 124 | break 125 | } 126 | if s.Target > 0 { 127 | if s.ScorePlayer >= s.Target { 128 | return fmt.Sprintf("You won! The final score was %d-%d", s.ScorePlayer, s.ScoreEnemy) 129 | } 130 | if s.ScoreEnemy >= s.Target { 131 | return fmt.Sprintf("You lost! The final score was %d-%d", s.ScoreEnemy, s.ScorePlayer) 132 | } 133 | } 134 | update(s) 135 | } 136 | } 137 | 138 | //ballMove 139 | func ballMove(s state) state { 140 | prevX := s.Ball.Point().X 141 | s.Ball.Next() 142 | s.Shadows = nextShadow(s.Shadows, s.Ball) 143 | nextX := s.Ball.Point().X 144 | s = updateStatus(s) 145 | if _width/2 >= s.Ball.Point().X && prevX < nextX { 146 | s.Ball.Move(1, 0) 147 | s = updateStatus(s) 148 | } else if _width/2 <= s.Ball.Point().X && prevX > nextX { 149 | s.Ball.Move(-1, 0) 150 | s = updateStatus(s) 151 | } 152 | return s 153 | } 154 | 155 | //enemyMove 156 | func enemyMove(enemy Objective, ball Objective, player Objective) int { 157 | if ball.Point().X <= _width/2 { 158 | if enemy.Point().Y+2 < ball.Point().Y { 159 | return 1 160 | } else if enemy.Point().Y+2 > ball.Point().Y { 161 | return -1 162 | } 163 | return 0 164 | } 165 | p := (player.Point().Y + ball.Point().Y) / 2 166 | if int(math.Abs(float64(enemy.Point().Y-p))) < 1 { 167 | return 0 168 | } else if enemy.Point().Y <= p { 169 | return 1 170 | } else if enemy.Point().Y >= p { 171 | return -1 172 | } 173 | return 0 174 | } 175 | 176 | //updateStatus 177 | func updateStatus(s state) state { 178 | if s.Ball.Collision(s.Player) || s.Ball.Collision(s.Enemy) { 179 | s.Ball.Prev() 180 | s.Shadows = nextShadow(s.Shadows, s.Ball) 181 | s.Ball.Turn(VERTICAL) 182 | s.Ball.Next() 183 | s.Shadows = nextShadow(s.Shadows, s.Ball) 184 | s.Count++ 185 | } 186 | if s.Ball.Collision(s.TopLine) || s.Ball.Collision(s.BottomLine) { 187 | s.Ball.Prev() 188 | s.Shadows = nextShadow(s.Shadows, s.Ball) 189 | s.Ball.Turn(HORIZONAL) 190 | s.Ball.Next() 191 | s.Shadows = nextShadow(s.Shadows, s.Ball) 192 | } 193 | if s.Ball.Collision(s.LeftLine) { 194 | s.Ball = inirBall() 195 | s.ScorePlayer++ 196 | s.Count = 0 197 | } 198 | 199 | if s.Ball.Collision(s.RightLine) { 200 | s.Ball = inirBall() 201 | s.ScoreEnemy++ 202 | s.Count = 0 203 | } 204 | return s 205 | } 206 | 207 | //initState 208 | func initState(keyword string, target int) state { 209 | s := state{} 210 | _width, _height = termbox.Size() 211 | s.TopLine = NewCollisionableObject(0, 1, _width, 1, "-") 212 | s.BottomLine = NewCollisionableObject(0, _height-2, _width, 1, "-") 213 | s.LeftLine = NewCollisionableObject(0, 0, 1, _height, " ") 214 | s.RightLine = NewCollisionableObject(_width-1, 0, 1, _height, " ") 215 | s.Player = NewCollisionableMovableObject(_width-3, _height/2-2, 2, 4, "|", 0, 0) 216 | s.Enemy = NewCollisionableMovableObject(1, _height/2-2, 2, 4, "|", 0, 0) 217 | s.Ball = inirBall() 218 | s.Target = target 219 | for i := range keyword { 220 | s.Shadows = append(s.Shadows, NewMovableObject(s.Ball.Point().X, s.Ball.Point().Y, s.Ball.Size().Width, s.Ball.Size().Height, string(keyword[i]), 0, 0)) 221 | } 222 | return s 223 | } 224 | 225 | //nextShadow 226 | func nextShadow(shadow []MovableObject, ball CollisionableMovableObject) []MovableObject { 227 | for i := len(shadow) - 1; i >= 0; i-- { 228 | if i == 0 { 229 | shadow[i] = NewMovableObject(ball.Point().X, ball.Point().Y, 1, 1, shadow[i].Str(), 0, 0) 230 | } else { 231 | shadow[i] = NewMovableObject(shadow[i-1].Point().X, shadow[i-1].Point().Y, 1, 1, shadow[i].Str(), 0, 0) 232 | } 233 | } 234 | return shadow 235 | } 236 | 237 | //inirBall 238 | func inirBall() CollisionableMovableObject { 239 | rand.Seed(time.Now().UnixNano()) 240 | r1 := rand.Intn(_height / 3) 241 | vec := 0 242 | if rand.Intn(100) <= 50 { 243 | vec = 1 244 | } else { 245 | vec = -1 246 | } 247 | return NewCollisionableMovableObject(_width/2, _height/3+r1, 1, 1, " ", -1, vec) 248 | } 249 | 250 | //start 251 | func start(keyword string, target int) { 252 | err := termbox.Init() 253 | if err != nil { 254 | panic(err) 255 | } 256 | 257 | s := initState(keyword, target) 258 | 259 | kch := make(chan termbox.Key) 260 | tch := make(chan bool) 261 | go keyEventLoop(kch) 262 | go timerLoop(tch) 263 | message := controller(s, kch, tch) 264 | termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) 265 | 266 | termbox.Close() 267 | 268 | if message != "" { 269 | fmt.Println(message) 270 | } 271 | } 272 | --------------------------------------------------------------------------------