mirror of
https://github.com/abakh/nbsdgames
synced 2025-04-28 14:09:32 -04:00
441 lines
14 KiB
Python
441 lines
14 KiB
Python
from __future__ import generators
|
|
import os, math, random
|
|
import images, gamesrv
|
|
from images import ActiveSprite
|
|
from boards import CELL, HALFCELL, bget
|
|
from mnstrmap import GreenAndBlue
|
|
from bubbles import BubblingEyes, Bubble
|
|
from bonuses import Bonus, points
|
|
|
|
LocalDir = os.path.basename(os.path.dirname(__file__))
|
|
|
|
|
|
localmap = {
|
|
't-brick1': ('image1-%d.ppm', (0, 0, 16, 16)),
|
|
't-brick2': ('image1-%d.ppm', (16, 0, 16, 16)),
|
|
}
|
|
|
|
music = gamesrv.getmusic(os.path.join(LocalDir, 'music.wav'))
|
|
|
|
|
|
class BrickEyes(BubblingEyes):
|
|
|
|
Patterns = [[(-2,0), (-1,0), (0,0), (1,0)],
|
|
[(-2,0), (-1,0), (0,0), (0,-1)],
|
|
[(-1,-1), (-1,0), (0,0), (1,0)],
|
|
[(-1,0), (0,0), (0,-1), (1,0)],
|
|
[(-1,-1), (0,-1), (0,0), (1,0)],
|
|
[(-2,0), (-1,0), (-1,-1), (0,-1)],
|
|
[(-1,-1), (-1,0), (0,-1), (0,0)]]
|
|
|
|
def __init__(self, tetris, bubber, saved_caps, olddragon):
|
|
BubblingEyes.__init__(self, bubber, saved_caps, olddragon)
|
|
self.tetris = tetris
|
|
self.bricks = []
|
|
|
|
def playing_bubble(self, oldsprite):
|
|
import boards
|
|
self.pat = random.choice(self.Patterns)
|
|
self.orientation = 1,0
|
|
xmin = 2 - min([x for x,y in self.pat])
|
|
xmax = boards.width-3 - max([x for x,y in self.pat])
|
|
x = int(random.normalvariate(oldsprite.x, boards.bwidth/4))
|
|
x = (x+HALFCELL) // CELL
|
|
if x<xmin: x=xmin
|
|
if x>xmax: x=xmax
|
|
y = -1
|
|
self.tx = x
|
|
self.ty = y
|
|
self.move((x-1)*CELL, (y-1)*CELL)
|
|
for i in range(5):
|
|
yield None
|
|
self.bricks = [Brick(self.bubber, px, py)
|
|
for px, py in self.brick_positions()]
|
|
self.gen.append(self.step_control())
|
|
self.gen.append(self.rotate_control())
|
|
self.gen.append(self.fall_control())
|
|
self.gen.append(self.move_eyes())
|
|
|
|
def bottom_up(self):
|
|
return 0
|
|
|
|
def kill(self):
|
|
for b in self.bricks:
|
|
b.stop(self.tetris)
|
|
b.remove()
|
|
self.bricks = []
|
|
BubblingEyes.kill(self)
|
|
|
|
def brick_positions(self):
|
|
ox, oy = self.orientation
|
|
result = []
|
|
cx = self.tx*CELL - HALFCELL
|
|
cy = self.ty*CELL - HALFCELL
|
|
for px, py in self.pat:
|
|
px = px*CELL + HALFCELL
|
|
py = py*CELL + HALFCELL
|
|
result.append((cx+px*ox-py*oy, cy+px*oy+py*ox))
|
|
return result
|
|
|
|
def save_position(self):
|
|
return self.tx, self.ty, self.orientation
|
|
|
|
def restore_position(self, p):
|
|
self.tx, self.ty, self.orientation = p
|
|
|
|
def moved(self, old_position):
|
|
for b in self.bricks:
|
|
b.set(' ')
|
|
try:
|
|
for px, py in self.brick_positions():
|
|
if bget(px//CELL, py//CELL) != ' ':
|
|
self.restore_position(old_position)
|
|
return 0
|
|
for b, (px, py) in zip(self.bricks, self.brick_positions()):
|
|
b.follow(px, py)
|
|
finally:
|
|
for b in self.bricks:
|
|
b.set('!') # note: we need '!' < '#'
|
|
return 1
|
|
|
|
def onground(self):
|
|
for b in self.bricks:
|
|
if b.gen: # brick still moving
|
|
return 0
|
|
for px, py in self.brick_positions():
|
|
if bget(px//CELL, py//CELL+1) >= '#':
|
|
return 1
|
|
return 0
|
|
|
|
def step_control(self):
|
|
while 1:
|
|
while not self.bubber.wannago(self.dcap):
|
|
yield None
|
|
pos = self.save_position()
|
|
self.tx += self.bubber.wannago(self.dcap)
|
|
if self.moved(pos):
|
|
for i in range(4):
|
|
yield None
|
|
yield None
|
|
|
|
def fall_control(self):
|
|
delay = 1
|
|
while 1:
|
|
for i in range(delay and 14):
|
|
if self.bubber.key_fire:
|
|
break
|
|
yield None
|
|
pos = self.save_position()
|
|
self.ty += 1
|
|
delay = self.moved(pos)
|
|
if delay:
|
|
for i in range(3):
|
|
yield None
|
|
elif self.onground() and self.tetris.ready:
|
|
self.gen = [self.stopping()]
|
|
yield None
|
|
|
|
def rotate_control(self):
|
|
while 1:
|
|
while not self.bubber.key_jump:
|
|
yield None
|
|
pos = self.save_position()
|
|
ox, oy = self.orientation
|
|
self.orientation = oy, -ox
|
|
if self.moved(pos):
|
|
for i in range(7):
|
|
yield None
|
|
yield None
|
|
|
|
def stopping(self):
|
|
self.move(self.x, -self.ico.h)
|
|
positions = [(py//CELL, px//CELL) for px, py in self.brick_positions()
|
|
if py >= 0]
|
|
positions.sort()
|
|
positions = [(px, py) for py, px in positions]
|
|
for b in self.bricks:
|
|
b.stop(self.tetris)
|
|
if b.ty < 0:
|
|
b.remove()
|
|
self.bricks = []
|
|
staticbricks = self.tetris.staticbricks
|
|
pts = 500
|
|
while 1:
|
|
for px, py in positions:
|
|
y = py
|
|
x1 = px
|
|
while (x1-1, y) in staticbricks:
|
|
x1 -= 1
|
|
if bget(x1-1, y) != '#':
|
|
continue
|
|
x2 = px
|
|
while (x2, y) in staticbricks:
|
|
x2 += 1
|
|
if bget(x2, y) != '#':
|
|
continue
|
|
if x2-x1 < 2:
|
|
continue
|
|
# full line
|
|
ico = images.sprget(Bubble.exploding_bubbles[0])
|
|
self.tetris.score[self.bubber] = self.tetris.score.get(
|
|
self.bubber, 0) + 1
|
|
xlist = range(x1, x2)
|
|
for x in xlist:
|
|
s = ActiveSprite(ico,
|
|
x*CELL + random.randrange(CELL) - CELL,
|
|
y*CELL + random.randrange(CELL) - CELL)
|
|
s.gen.append(s.die(Bubble.exploding_bubbles))
|
|
s = staticbricks[x, y]
|
|
points(x*CELL + HALFCELL, y*CELL + HALFCELL, s, pts)
|
|
s.remove()
|
|
if pts == 500:
|
|
self.play(images.Snd.Fruit)
|
|
elif pts == 4000:
|
|
self.play(images.Snd.Extralife)
|
|
else:
|
|
self.play(images.Snd.Extra)
|
|
pts *= 2
|
|
for y in range(py-1, -1, -1):
|
|
if not [x for x in xlist if (x, y) in staticbricks]:
|
|
break
|
|
for t in range(4):
|
|
yield None
|
|
if [x for x in xlist if (x, y+1) in staticbricks]:
|
|
break
|
|
for x in xlist:
|
|
if (x, y) in staticbricks:
|
|
staticbricks[x, y].shiftdown()
|
|
yield None
|
|
break
|
|
else:
|
|
break
|
|
if self.tetris.ready < 2:
|
|
self.gen.append(self.playing_bubble(self))
|
|
|
|
def move_eyes(self):
|
|
while 1:
|
|
tx = (self.tx-1) * CELL
|
|
ty = (self.ty-1) * CELL
|
|
for i in range(3):
|
|
if tx < self.x:
|
|
dx = -1
|
|
elif tx > self.x:
|
|
dx = +1
|
|
else:
|
|
dx = 0
|
|
if ty > self.y:
|
|
dy = +1
|
|
else:
|
|
dy = 0
|
|
self.step(2*dx, 2*dy)
|
|
key = ('eyes', dx, 0)
|
|
self.seticon(images.sprget(key))
|
|
yield None
|
|
|
|
|
|
class Brick(ActiveSprite):
|
|
|
|
def __init__(self, bubber, x, y):
|
|
ico = images.sprget(('t-brick1', bubber.pn))
|
|
ActiveSprite.__init__(self, ico, x, y)
|
|
self.tx = x//CELL
|
|
self.ty = y//CELL
|
|
self.bubber = bubber
|
|
|
|
def follow(self, x, y):
|
|
self.tx = x//CELL
|
|
self.ty = y//CELL
|
|
self.gen = [self.following(x, y)]
|
|
|
|
def following(self, nx, ny):
|
|
dx = (nx - self.x) / 7.0
|
|
dy = (ny - self.y) / 7.0
|
|
for i in range(6, 0, -1):
|
|
self.move(nx - int(i*dx), ny - int(i*dy))
|
|
yield None
|
|
self.move(nx, ny)
|
|
|
|
def set(self, c):
|
|
x, y = self.tx, self.ty
|
|
if 0 <= x < curboard.width and 0 <= y < curboard.height:
|
|
line = curboard.walls[y]
|
|
curboard.walls[y] = line[:x] + c + line[x+1:]
|
|
|
|
def stop(self, tetris):
|
|
self.set('X')
|
|
self.seticon(images.sprget(('t-brick2', self.bubber.pn)))
|
|
images.ActiveSprites.remove(self)
|
|
tetris.staticbricks[self.tx, self.ty] = self
|
|
self.staticbricks = tetris.staticbricks
|
|
|
|
def remove(self):
|
|
del self.staticbricks[self.tx, self.ty]
|
|
self.set(' ')
|
|
gamesrv.Sprite.kill(self)
|
|
|
|
def shiftdown(self):
|
|
del self.staticbricks[self.tx, self.ty]
|
|
self.set(' ')
|
|
self.ty += 1
|
|
self.set('X')
|
|
self.staticbricks[self.tx, self.ty] = self
|
|
self.step(0, CELL)
|
|
|
|
|
|
class Tetris:
|
|
|
|
def bgen(self, limittime = 90.1): # 1:30
|
|
import boards
|
|
from player import BubPlayer
|
|
|
|
self.score = {}
|
|
for t in boards.initsubgame(music, self.displaypoints):
|
|
yield t
|
|
|
|
tc = boards.TimeCounter(limittime)
|
|
self.ready = 0
|
|
self.staticbricks = {}
|
|
finished = 0
|
|
for t in self.frame():
|
|
t = boards.normal_frame()
|
|
self.build_eyes()
|
|
yield t
|
|
tc.update(t)
|
|
if tc.time == 0.0:
|
|
self.ready = 2
|
|
finished += not self.still_playing()
|
|
if finished > 16:
|
|
break
|
|
if (BubPlayer.FrameCounter & 15) == 7:
|
|
for s in images.ActiveSprites:
|
|
if isinstance(s, Bubble):
|
|
s.pop()
|
|
elif isinstance(s, Bonus):
|
|
s.kill()
|
|
|
|
tc.restore()
|
|
for t in boards.result_ranking(self.score):
|
|
self.remove_eyes()
|
|
yield t
|
|
for s in self.staticbricks.values():
|
|
s.remove()
|
|
|
|
def displaypoints(self, bubber):
|
|
return self.score.get(bubber, 0)
|
|
|
|
def frame(self):
|
|
heights = {1: curboard.height,
|
|
curboard.width-2: curboard.height}
|
|
ymax = curboard.height-1
|
|
maxheight = curboard.height*3//4
|
|
for x in range(2, curboard.width-2):
|
|
if bget(x, ymax) == ' ':
|
|
curboard.putwall(x, ymax)
|
|
height = 1
|
|
for y in range(ymax-1, -1, -1):
|
|
if bget(x, y) == '#':
|
|
if height == maxheight:
|
|
curboard.killwall(x, y)
|
|
else:
|
|
height += 1
|
|
heights[x] = height
|
|
xlist = range(2, curboard.width-2)
|
|
random.shuffle(xlist)
|
|
for x in xlist:
|
|
h = heights[x]
|
|
x1 = x2 = x
|
|
while heights[x1-1] == h:
|
|
x1 -= 1
|
|
while heights[x2] == h:
|
|
x2 += 1
|
|
parts = (x2-x1) // 8
|
|
if not parts:
|
|
continue
|
|
left = 0
|
|
if heights[x1-1] > h:
|
|
x1 -= 1
|
|
left += 1
|
|
right = parts+1
|
|
if heights[x2] > h:
|
|
x2 += 1
|
|
right -= 1
|
|
for p in range(left, right):
|
|
x = x1 + ((x2-x1-1)*p+parts//2)//parts
|
|
y = ymax
|
|
for i in range(2):
|
|
while bget(x, y) == '#':
|
|
y -= 1
|
|
if y >= 3:
|
|
curboard.putwall(x, y)
|
|
heights[x] += 1
|
|
curboard.reorder_walls()
|
|
|
|
walls_by_pos = curboard.walls_by_pos
|
|
moves = 1
|
|
s = 8.0
|
|
while moves:
|
|
moves = 0
|
|
for y in range(curboard.height-3, -1, -1):
|
|
for x in range(2, curboard.width-2):
|
|
if ((y,x) in walls_by_pos and
|
|
(y+1,x) not in walls_by_pos):
|
|
y0 = y
|
|
while (y0-1,x) in walls_by_pos:
|
|
y0 -= 1
|
|
w = curboard.killwall(x, y0, 0)
|
|
curboard.putwall(x, y+1, w)
|
|
moves = 1
|
|
curboard.reorder_walls()
|
|
for i in range(int(s)+2):
|
|
yield None
|
|
s *= 0.95
|
|
self.ready = 1
|
|
while 1:
|
|
yield None
|
|
|
|
def build_eyes(self):
|
|
from player import BubPlayer
|
|
for p in BubPlayer.PlayerList:
|
|
dragons = [d for d in p.dragons if not isinstance(d, BrickEyes)]
|
|
if dragons and len(p.dragons) == len(dragons):
|
|
dragon = random.choice(dragons)
|
|
eyes = BrickEyes(self, p, dragon.dcap, dragon)
|
|
p.dragons.append(eyes)
|
|
#p.emotic(dragon, 4)
|
|
for d in dragons:
|
|
d.kill()
|
|
|
|
def still_playing(self):
|
|
from player import BubPlayer
|
|
for p in BubPlayer.PlayerList:
|
|
for d in p.dragons:
|
|
if d.gen:
|
|
return 1
|
|
return 0
|
|
|
|
def remove_eyes(self):
|
|
from player import BubPlayer
|
|
for p in BubPlayer.PlayerList:
|
|
for d in p.dragons:
|
|
d.kill()
|
|
|
|
# This game is suitable for at least min_players players
|
|
min_players = 1
|
|
|
|
def run():
|
|
global curboard
|
|
import boards
|
|
from boards import curboard
|
|
boards.replace_boardgen(Tetris().bgen())
|
|
|
|
def setup():
|
|
from player import BubPlayer
|
|
for key, (filename, rect) in localmap.items():
|
|
filename = os.path.join(LocalDir, filename)
|
|
if filename.find('%d') >= 0:
|
|
for p in BubPlayer.PlayerList:
|
|
images.sprmap[key, p.pn] = (filename % p.pn, rect)
|
|
else:
|
|
images.sprmap[key] = (filename, rect)
|
|
setup()
|