// 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(int64(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(); }