Skip to content

Instantly share code, notes, and snippets.

@mackee
Created March 4, 2022 13:54
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mackee/d877f31d82871d2ec2e10b6f5e8fff1e to your computer and use it in GitHub Desktop.
Save mackee/d877f31d82871d2ec2e10b6f5e8fff1e to your computer and use it in GitHub Desktop.
The `perl` fall block game
module github.com/mackee/sandbox/goperlfallblock
go 1.16
require (
github.com/JoelOtter/termloop v0.0.0-20210806173944-5f7c38744afb // indirect
github.com/Songmu/strrand v0.0.0-20181014100012-5195340ba52c // indirect
github.com/nsf/termbox-go v1.1.1 // indirect
)
github.com/JoelOtter/termloop v0.0.0-20210806173944-5f7c38744afb h1:wXR5fXM/+4VFARcWVtjwb0wxfQl5RxemkNzzs2Jb918=
github.com/JoelOtter/termloop v0.0.0-20210806173944-5f7c38744afb/go.mod h1:Tie7OOEgasw91JpzA8UywemPyGehxZ06Gqtl5B1/vXI=
github.com/Songmu/strrand v0.0.0-20181014100012-5195340ba52c h1:EoNWRkd+8wioWv5fo8RhGwrRSdqlo0NelrFe7gadIL8=
github.com/Songmu/strrand v0.0.0-20181014100012-5195340ba52c/go.mod h1:4WdL9c/3T0wSIZNyXpFdDVYqGukpn/18nmcXw1QwED8=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
package main
import (
"os/exec"
"strings"
"time"
tl "github.com/JoelOtter/termloop"
"github.com/Songmu/strrand"
)
type Next struct {
*tl.Entity
cs [2]rune
}
func NewNext(e *tl.Entity) *Next {
cs, err := generateChars()
if err != nil {
panic(err)
}
return &Next{
Entity: e,
cs: cs,
}
}
func (n *Next) SetCells() {
c1 := &tl.Cell{Fg: tl.ColorWhite | tl.AttrBold, Ch: n.cs[0]}
c2 := &tl.Cell{Fg: tl.ColorWhite | tl.AttrBold, Ch: n.cs[1]}
n.SetCell(0, 0, c1)
n.SetCell(1, 0, c2)
}
func (n *Next) Tick(ev tl.Event) {
if n.cs[0] == ' ' && n.cs[1] == ' ' {
cs, err := generateChars()
if err != nil {
panic(err)
}
n.cs = cs
n.SetCells()
}
}
type Plane struct {
*tl.Entity
plane [8][10]rune
dirty bool
current [2]rune
pos [2][2]int
ticker *time.Ticker
next *Next
gameover bool
errors [10]string
}
func NewPlane(e *tl.Entity, next *Next) *Plane {
ticker := time.NewTicker(1000 * time.Millisecond)
p := &Plane{
Entity: e,
dirty: true,
ticker: ticker,
current: [2]rune{' ', ' '},
next: next,
}
for x := 0; x < 8; x++ {
for y := 0; y < 10; y++ {
p.plane[x][y] = ' '
}
}
p.Fill(&tl.Cell{Bg: tl.ColorBlack, Ch: ' '})
return p
}
func (p *Plane) SetRune(x, y int, c rune) {
if c == ' ' {
return
}
p.dirty = true
p.plane[x][y] = c
}
func (p *Plane) HasRune(x, y int) bool {
return p.plane[x][y] != ' '
}
func (p *Plane) RuneCollide() {
for i := 0; i < 2; i++ {
x, y := p.pos[i][0], p.pos[i][1]
if y < 9 && p.HasRune(x, y+1) {
p.SetRune(x, y, p.current[i])
p.current[i] = ' '
} else {
if y < 9 {
p.pos[i][1]++
} else {
p.SetRune(x, y, p.current[i])
p.current[i] = ' '
}
}
}
if p.current[0] == ' ' && p.current[1] == ' ' {
p.PullFromNext()
}
}
func (p *Plane) PullFromNext() {
p.ParseCheck()
if p.HasRune(3, 0) || p.HasRune(4, 0) {
p.gameover = true
}
p.current = p.next.cs
p.pos[0], p.pos[1] = [2]int{3, 0}, [2]int{4, 0}
p.next.cs = [2]rune{' ', ' '}
}
func (p *Plane) Reset() {
for x := 0; x < 8; x++ {
for y := 0; y < 10; y++ {
p.plane[x][y] = ' '
}
}
for y := 0; y < 10; y++ {
p.errors[y] = ""
}
p.gameover = false
}
func (p *Plane) Tick(ev tl.Event) {
if p.gameover {
if ev.Type == tl.EventKey && ev.Key == tl.KeySpace {
p.Reset()
p.PullFromNext()
}
return
}
if ev.Type == tl.EventKey {
switch ev.Key {
case tl.KeyArrowLeft:
if p.pos[0][0] > 0 && !p.HasRune(p.pos[0][0]-1, p.pos[0][1]) {
p.pos[0][0]--
p.pos[1][0]--
}
case tl.KeyArrowRight:
if p.pos[1][0] < 7 && !p.HasRune(p.pos[1][0]+1, p.pos[1][1]) {
p.pos[0][0]++
p.pos[1][0]++
}
case tl.KeyArrowUp:
p.current[0], p.current[1] = p.current[1], p.current[0]
case tl.KeyArrowDown:
p.RuneCollide()
}
} else {
select {
case <-p.ticker.C:
p.RuneCollide()
default:
return
}
}
for x := 0; x < 8; x++ {
for y := 0; y < 10; y++ {
color := tl.ColorWhite | tl.AttrBold
if p.errors[y] != "" {
color = tl.ColorRed | tl.AttrBold
}
c := &tl.Cell{
Fg: color,
Bg: tl.ColorBlack,
Ch: p.plane[x][y],
}
p.SetCell(x, y, c)
}
}
for i := 0; i < 2; i++ {
if p.current[i] == ' ' {
continue
}
c := &tl.Cell{
Fg: tl.ColorWhite | tl.AttrBold,
Bg: tl.ColorBlack,
Ch: p.current[i],
}
p.SetCell(p.pos[i][0], p.pos[i][1], c)
}
}
func (p *Plane) ParseCheck() {
for y := 0; y < 10; y++ {
if p.errors[y] != "" {
continue
}
filled := true
b := &strings.Builder{}
for x := 0; x < 8; x++ {
if p.plane[x][y] == ' ' {
filled = false
break
}
b.WriteRune(p.plane[x][y])
}
if filled {
result := checkPerl(b.String())
if result == "" {
for x := 0; x < 8; x++ {
sl := append([]rune{' '}, p.plane[x][:y]...)
sl = append(sl, p.plane[x][y+1:]...)
copy(p.plane[x][:], sl[:10])
}
return
}
p.errors[y] = result
return
}
}
}
func main() {
game := tl.NewGame()
level := tl.NewBaseLevel(tl.Cell{
Bg: tl.ColorBlack,
Fg: tl.ColorWhite,
Ch: ' ',
})
var errors [10]string
// Plane
level.AddEntity(tl.NewRectangle(19, 3, 12, 12, tl.ColorCyan))
next := NewNext(tl.NewEntity(9, 6, 2, 1))
plane := NewPlane(tl.NewEntity(21, 4, 8, 10), next)
plane.errors = errors
level.AddEntity(plane)
// Next
level.AddEntity(tl.NewRectangle(3, 4, 14, 5, tl.ColorCyan))
level.AddEntity(tl.NewRectangle(5, 5, 10, 3, tl.ColorBlack))
next.SetCells()
level.AddEntity(next)
nextLabel := tl.NewText(8, 4, "NEXT", tl.ColorBlack|tl.AttrBold, tl.ColorWhite)
level.AddEntity(nextLabel)
// Errors
level.AddEntity(tl.NewRectangle(35, 4, 50, 10, tl.ColorBlack))
game.Screen().SetLevel(level)
game.Start()
}
func generateChars() ([2]rune, error) {
str, err := strrand.RandomString(`[$@%\+\-\*.]{2}`)
if err != nil {
return [2]rune{}, err
}
var cs [2]rune
for i, s := range str {
cs[i] = s
}
return cs, nil
}
func checkPerl(line string) string {
cmd := exec.Command("perl", "-c", "-e", line)
if _, err := cmd.Output(); err != nil {
if ee, ok := err.(*exec.ExitError); ok {
return string(ee.Stderr)
}
panic(err)
}
return ""
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment