blob: 93509ccd88007b695c527e735471ee3b537f411c [file] [log] [blame]
// games/4s - a tetris clone
//
// Derived from Plan 9's /sys/src/games/xs.c
// http://plan9.bell-labs.com/sources/plan9/sys/src/games/xs.c
//
// Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights Reserved.
// Portions Copyright 2009 The Go Authors. All Rights Reserved.
// Distributed under the terms of the Lucent Public License Version 1.02
// See http://plan9.bell-labs.com/plan9/license.html
/*
* engine for 4s, 5s, etc
*/
package main
import (
"exp/draw";
"image";
"log";
"os";
"rand";
"time";
)
/*
Cursor whitearrow = {
{0, 0},
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC,
0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC,
0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
{0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C,
0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C,
0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C,
0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
};
*/
const (
CNone = 0;
CBounds = 1;
CPiece = 2;
NX = 10;
NY = 20;
NCOL = 10;
MAXN = 5;
)
var (
N int;
display draw.Context;
screen draw.Image;
screenr draw.Rectangle;
board [NY][NX]byte;
rboard draw.Rectangle;
pscore draw.Point;
scoresz draw.Point;
pcsz = 32;
pos draw.Point;
bbr, bb2r draw.Rectangle;
bb, bbmask, bb2, bb2mask *image.RGBA;
whitemask image.Image;
br, br2 draw.Rectangle;
points int;
dt int;
DY int;
DMOUSE int;
lastmx int;
mouse draw.Mouse;
newscreen bool;
timerc <-chan int64;
suspc chan bool;
mousec chan draw.Mouse;
resizec <-chan bool;
kbdc chan int;
suspended bool;
tsleep int;
piece *Piece;
pieces []Piece;
)
type Piece struct {
rot int;
tx int;
sz draw.Point;
d []draw.Point;
left *Piece;
right *Piece;
}
var txbits = [NCOL][32]byte{
[32]byte{0xDD, 0xDD, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF,
0xDD, 0xDD, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF,
0xDD, 0xDD, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF,
0xDD, 0xDD, 0xFF, 0xFF, 0x77, 0x77, 0xFF, 0xFF,
},
[32]byte{0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
},
[32]byte{0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
},
[32]byte{0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
0xAA, 0xAA, 0x55, 0x55, 0xAA, 0xAA, 0x55, 0x55,
},
[32]byte{0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
},
[32]byte{0x22, 0x22, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00,
0x22, 0x22, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00,
0x22, 0x22, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00,
0x22, 0x22, 0x00, 0x00, 0x88, 0x88, 0x00, 0x00,
},
[32]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
},
[32]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
},
[32]byte{0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
},
[32]byte{0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33,
0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33,
0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33,
0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33,
},
}
var txpix = [NCOL]draw.Color{
draw.Yellow, /* yellow */
draw.Cyan, /* cyan */
draw.Green, /* lime green */
draw.GreyBlue, /* slate */
draw.Red, /* red */
draw.GreyGreen, /* olive green */
draw.Blue, /* blue */
draw.Color(0xFF55AAFF), /* pink */
draw.Color(0xFFAAFFFF), /* lavender */
draw.Color(0xBB005DFF), /* maroon */
}
func movemouse() int {
//mouse.draw.Point = draw.Pt(rboard.Min.X + rboard.Dx()/2, rboard.Min.Y + rboard.Dy()/2);
//moveto(mousectl, mouse.Xy);
return mouse.X
}
func warp(p draw.Point, x int) int {
if !suspended && piece != nil {
x = pos.X + piece.sz.X*pcsz/2;
if p.Y < rboard.Min.Y {
p.Y = rboard.Min.Y
}
if p.Y >= rboard.Max.Y {
p.Y = rboard.Max.Y - 1
}
//moveto(mousectl, draw.Pt(x, p.Y));
}
return x;
}
func initPieces() {
for i := range pieces {
p := &pieces[i];
if p.rot == 3 {
p.right = &pieces[i-3]
} else {
p.right = &pieces[i+1]
}
if p.rot == 0 {
p.left = &pieces[i+3]
} else {
p.left = &pieces[i-1]
}
}
}
func collide(pt draw.Point, p *Piece) bool {
pt.X = (pt.X - rboard.Min.X) / pcsz;
pt.Y = (pt.Y - rboard.Min.Y) / pcsz;
for _, q := range p.d {
pt.X += q.X;
pt.Y += q.Y;
if pt.X < 0 || pt.X >= NX || pt.Y < 0 || pt.Y >= NY {
return true;
continue;
}
if board[pt.Y][pt.X] != 0 {
return true
}
}
return false;
}
func collider(pt, pmax draw.Point) bool {
pi := (pt.X - rboard.Min.X) / pcsz;
pj := (pt.Y - rboard.Min.Y) / pcsz;
n := pmax.X / pcsz;
m := pmax.Y/pcsz + 1;
for i := pi; i < pi+n && i < NX; i++ {
for j := pj; j < pj+m && j < NY; j++ {
if board[j][i] != 0 {
return true
}
}
}
return false;
}
func setpiece(p *Piece) {
draw.Draw(bb, bbr, draw.White, nil, draw.ZP);
draw.Draw(bbmask, bbr, draw.Transparent, nil, draw.ZP);
br = draw.Rect(0, 0, 0, 0);
br2 = br;
piece = p;
if p == nil {
return
}
var op draw.Point;
var r draw.Rectangle;
r.Min = bbr.Min;
for i, pt := range p.d {
r.Min.X += pt.X * pcsz;
r.Min.Y += pt.Y * pcsz;
r.Max.X = r.Min.X + pcsz;
r.Max.Y = r.Min.Y + pcsz;
if i == 0 {
draw.Draw(bb, r, draw.Black, nil, draw.ZP);
draw.Draw(bb, r.Inset(1), txpix[piece.tx], nil, draw.ZP);
draw.Draw(bbmask, r, draw.Opaque, nil, draw.ZP);
op = r.Min;
} else {
draw.Draw(bb, r, bb, nil, op);
draw.Draw(bbmask, r, bbmask, nil, op);
}
if br.Max.X < r.Max.X {
br.Max.X = r.Max.X
}
if br.Max.Y < r.Max.Y {
br.Max.Y = r.Max.Y
}
}
br.Max = br.Max.Sub(bbr.Min);
delta := draw.Pt(0, DY);
br2.Max = br.Max.Add(delta);
r = br.Add(bb2r.Min);
r2 := br2.Add(bb2r.Min);
draw.Draw(bb2, r2, draw.White, nil, draw.ZP);
draw.Draw(bb2, r.Add(delta), bb, nil, bbr.Min);
draw.Draw(bb2mask, r2, draw.Transparent, nil, draw.ZP);
draw.Draw(bb2mask, r, draw.Opaque, bbmask, bbr.Min);
draw.Draw(bb2mask, r.Add(delta), draw.Opaque, bbmask, bbr.Min);
}
func drawpiece() {
draw.Draw(screen, br.Add(pos), bb, bbmask, bbr.Min);
if suspended {
draw.Draw(screen, br.Add(pos), draw.White, whitemask, draw.ZP)
}
}
func undrawpiece() {
var mask image.Image;
if collider(pos, br.Max) {
mask = bbmask
}
draw.Draw(screen, br.Add(pos), draw.White, mask, bbr.Min);
}
func rest() {
pt := pos.Sub(rboard.Min).Div(pcsz);
for _, p := range piece.d {
pt.X += p.X;
pt.Y += p.Y;
board[pt.Y][pt.X] = byte(piece.tx + 16);
}
}
func canfit(p *Piece) bool {
var dx = [...]int{0, -1, 1, -2, 2, -3, 3, 4, -4};
j := N + 1;
if j >= 4 {
j = p.sz.X;
if j < p.sz.Y {
j = p.sz.Y
}
j = 2*j - 1;
}
for i := 0; i < j; i++ {
var z draw.Point;
z.X = pos.X + dx[i]*pcsz;
z.Y = pos.Y;
if !collide(z, p) {
z.Y = pos.Y + pcsz - 1;
if !collide(z, p) {
undrawpiece();
pos.X = z.X;
return true;
}
}
}
return false;
}
func score(p int) {
points += p
// snprint(buf, sizeof(buf), "%.6ld", points);
// draw.Draw(screen, draw.Rpt(pscore, pscore.Add(scoresz)), draw.White, nil, draw.ZP);
// string(screen, pscore, draw.Black, draw.ZP, font, buf);
}
func drawsq(b draw.Image, p draw.Point, ptx int) {
var r draw.Rectangle;
r.Min = p;
r.Max.X = r.Min.X + pcsz;
r.Max.Y = r.Min.Y + pcsz;
draw.Draw(b, r, draw.Black, nil, draw.ZP);
draw.Draw(b, r.Inset(1), txpix[ptx], nil, draw.ZP);
}
func drawboard() {
draw.Border(screen, rboard.Inset(-2), 2, draw.Black, draw.ZP);
draw.Draw(screen, draw.Rect(rboard.Min.X, rboard.Min.Y-2, rboard.Max.X, rboard.Min.Y),
draw.White, nil, draw.ZP);
for i := 0; i < NY; i++ {
for j := 0; j < NX; j++ {
if board[i][j] != 0 {
drawsq(screen, draw.Pt(rboard.Min.X+j*pcsz, rboard.Min.Y+i*pcsz), int(board[i][j]-16))
}
}
}
score(0);
if suspended {
draw.Draw(screen, screenr, draw.White, whitemask, draw.ZP)
}
}
func choosepiece() {
for {
i := rand.Intn(len(pieces));
setpiece(&pieces[i]);
pos = rboard.Min;
pos.X += rand.Intn(NX) * pcsz;
if !collide(draw.Pt(pos.X, pos.Y+pcsz-DY), piece) {
break
}
}
drawpiece();
display.FlushImage();
}
func movepiece() bool {
var mask image.Image;
if collide(draw.Pt(pos.X, pos.Y+pcsz), piece) {
return false
}
if collider(pos, br2.Max) {
mask = bb2mask
}
draw.Draw(screen, br2.Add(pos), bb2, mask, bb2r.Min);
pos.Y += DY;
display.FlushImage();
return true;
}
func suspend(s bool) {
suspended = s;
/*
if suspended {
setcursor(mousectl, &whitearrow);
} else {
setcursor(mousectl, nil);
}
*/
if !suspended {
drawpiece()
}
drawboard();
display.FlushImage();
}
func pause(t int) {
display.FlushImage();
for {
select {
case s := <-suspc:
if !suspended && s {
suspend(true)
} else if suspended && !s {
suspend(false);
lastmx = warp(mouse.Point, lastmx);
}
case <-timerc:
if suspended {
break
}
t -= tsleep;
if t < 0 {
return
}
case <-resizec:
//redraw(true);
case mouse = <-mousec:
case <-kbdc:
}
}
}
func horiz() bool {
var lev [MAXN]int;
h := 0;
for i := 0; i < NY; i++ {
for j := 0; board[i][j] != 0; j++ {
if j == NX-1 {
lev[h] = i;
h++;
break;
}
}
}
if h == 0 {
return false
}
r := rboard;
newscreen = false;
for j := 0; j < h; j++ {
r.Min.Y = rboard.Min.Y + lev[j]*pcsz;
r.Max.Y = r.Min.Y + pcsz;
draw.Draw(screen, r, draw.White, whitemask, draw.ZP);
display.FlushImage();
}
PlaySound(whoosh);
for i := 0; i < 3; i++ {
pause(250);
if newscreen {
drawboard();
break;
}
for j := 0; j < h; j++ {
r.Min.Y = rboard.Min.Y + lev[j]*pcsz;
r.Max.Y = r.Min.Y + pcsz;
draw.Draw(screen, r, draw.White, whitemask, draw.ZP);
}
display.FlushImage();
}
r = rboard;
for j := 0; j < h; j++ {
i := NY - lev[j] - 1;
score(250 + 10*i*i);
r.Min.Y = rboard.Min.Y;
r.Max.Y = rboard.Min.Y + lev[j]*pcsz;
draw.Draw(screen, r.Add(draw.Pt(0, pcsz)), screen, nil, r.Min);
r.Max.Y = rboard.Min.Y + pcsz;
draw.Draw(screen, r, draw.White, nil, draw.ZP);
for k := lev[j] - 1; k >= 0; k-- {
board[k+1] = board[k]
}
board[0] = [NX]byte{};
}
display.FlushImage();
return true;
}
func mright() {
if !collide(draw.Pt(pos.X+pcsz, pos.Y), piece) &&
!collide(draw.Pt(pos.X+pcsz, pos.Y+pcsz-DY), piece) {
undrawpiece();
pos.X += pcsz;
drawpiece();
display.FlushImage();
}
}
func mleft() {
if !collide(draw.Pt(pos.X-pcsz, pos.Y), piece) &&
!collide(draw.Pt(pos.X-pcsz, pos.Y+pcsz-DY), piece) {
undrawpiece();
pos.X -= pcsz;
drawpiece();
display.FlushImage();
}
}
func rright() {
if canfit(piece.right) {
setpiece(piece.right);
drawpiece();
display.FlushImage();
}
}
func rleft() {
if canfit(piece.left) {
setpiece(piece.left);
drawpiece();
display.FlushImage();
}
}
var fusst = 0
func drop(f bool) bool {
if f {
score(5 * (rboard.Max.Y - pos.Y) / pcsz);
for movepiece() {
}
}
fusst = 0;
rest();
if pos.Y == rboard.Min.Y && !horiz() {
return true
}
horiz();
setpiece(nil);
pause(1500);
choosepiece();
lastmx = warp(mouse.Point, lastmx);
return false;
}
func play() {
var om draw.Mouse;
dt = 64;
lastmx = -1;
lastmx = movemouse();
choosepiece();
lastmx = warp(mouse.Point, lastmx);
for {
select {
case mouse = <-mousec:
if suspended {
om = mouse;
break;
}
if lastmx < 0 {
lastmx = mouse.X
}
if mouse.X > lastmx+DMOUSE {
mright();
lastmx = mouse.X;
}
if mouse.X < lastmx-DMOUSE {
mleft();
lastmx = mouse.X;
}
if mouse.Buttons&^om.Buttons&1 == 1 {
rleft()
}
if mouse.Buttons&^om.Buttons&2 == 2 {
if drop(true) {
return
}
}
if mouse.Buttons&^om.Buttons&4 == 4 {
rright()
}
om = mouse;
case s := <-suspc:
if !suspended && s {
suspend(true)
} else if suspended && !s {
suspend(false);
lastmx = warp(mouse.Point, lastmx);
}
case <-resizec:
//redraw(true);
case r := <-kbdc:
if suspended {
break
}
switch r {
case 'f', ';':
mright()
case 'a', 'j':
mleft()
case 'd', 'l':
rright()
case 's', 'k':
rleft()
case ' ':
if drop(true) {
return
}
}
case <-timerc:
if suspended {
break
}
dt -= tsleep;
if dt < 0 {
i := 1;
dt = 16 * (points + rand.Intn(10000) - 5000) / 10000;
if dt >= 32 {
i += (dt - 32) / 16;
dt = 32;
}
dt = 52 - dt;
for ; i > 0; i-- {
if movepiece() {
continue
}
fusst++;
if fusst == 40 {
if drop(false) {
return
}
break;
}
}
}
}
}
}
func suspproc() {
mc := display.MouseChan();
kc := display.KeyboardChan();
s := false;
for {
select {
case mouse = <-mc:
mousec <- mouse
case r := <-kc:
switch r {
case 'q', 'Q', 0x04, 0x7F:
os.Exit(0)
default:
if s {
s = false;
suspc <- s;
break;
}
switch r {
case 'z', 'Z', 'p', 'P', 0x1B:
s = true;
suspc <- s;
default:
kbdc <- r
}
}
}
}
}
func redraw(new bool) {
// if new && getwindow(display, Refmesg) < 0 {
// sysfatal("can't reattach to window");
// }
r := draw.Rect(0, 0, screen.Width(), screen.Height());
pos.X = (pos.X - rboard.Min.X) / pcsz;
pos.Y = (pos.Y - rboard.Min.Y) / pcsz;
dx := r.Max.X - r.Min.X;
dy := r.Max.Y - r.Min.Y - 2*32;
DY = dx / NX;
if DY > dy/NY {
DY = dy / NY
}
DY /= 8;
if DY > 4 {
DY = 4
}
pcsz = DY * 8;
DMOUSE = pcsz / 3;
if pcsz < 8 {
log.Exitf("screen too small: %d", pcsz)
}
rboard = screenr;
rboard.Min.X += (dx - pcsz*NX) / 2;
rboard.Min.Y += (dy-pcsz*NY)/2 + 32;
rboard.Max.X = rboard.Min.X + NX*pcsz;
rboard.Max.Y = rboard.Min.Y + NY*pcsz;
pscore.X = rboard.Min.X + 8;
pscore.Y = rboard.Min.Y - 32;
// scoresz = stringsize(font, "000000");
pos.X = pos.X*pcsz + rboard.Min.X;
pos.Y = pos.Y*pcsz + rboard.Min.Y;
bbr = draw.Rect(0, 0, N*pcsz, N*pcsz);
bb = image.NewRGBA(bbr.Max.X, bbr.Max.Y);
bbmask = image.NewRGBA(bbr.Max.X, bbr.Max.Y); // actually just a bitmap
bb2r = draw.Rect(0, 0, N*pcsz, N*pcsz+DY);
bb2 = image.NewRGBA(bb2r.Dx(), bb2r.Dy());
bb2mask = image.NewRGBA(bb2r.Dx(), bb2r.Dy()); // actually just a bitmap
draw.Draw(screen, screenr, draw.White, nil, draw.ZP);
drawboard();
setpiece(piece);
if piece != nil {
drawpiece()
}
lastmx = movemouse();
newscreen = true;
display.FlushImage();
}
func quitter(c <-chan bool) {
<-c;
os.Exit(0);
}
func Play(pp []Piece, ctxt draw.Context) {
display = ctxt;
screen = ctxt.Screen();
screenr = draw.Rect(0, 0, screen.Width(), screen.Height());
pieces = pp;
N = len(pieces[0].d);
initPieces();
rand.Seed(int32(time.Nanoseconds() % (1e9 - 1)));
whitemask = draw.White.SetAlpha(0x7F);
tsleep = 50;
timerc = time.Tick(int64(tsleep/2) * 1e6);
suspc = make(chan bool);
mousec = make(chan draw.Mouse);
resizec = ctxt.ResizeChan();
kbdc = make(chan int);
go quitter(ctxt.QuitChan());
go suspproc();
points = 0;
redraw(false);
play();
}