MODULE----------------------------------------------- per Game initialization ---; IMPORT Point, Rect, VBT, Thread, Trestle, Random, Time, HVSplit; IMPORT TextVBT, Wr, Axis, ButtonVBT, BorderedVBT, Text, Latin1Key; IMPORT OSConfig, OSError, PaintOp, Params, Stdio, TSplit, Word, Region; IMPORT Fmt, FS, Split, TextWr, IO, Env, Font, Date; IMPORT IntArraySort, RigidVBT, Process, TrestleComm; IMPORT Config, ScoreFile, ScoreDir; CONST ALIAS = "GAMEALIAS"; MaxScores = 9; VGrain = 3; ScoreBasis = 0.656832d0; HeightPts = 10; SpeedUp = 0.95d0; SpeedUpRows = 5; MinCell = 5; Margin = 15; WHITE = 0; MaxRows = 24; MaxCols = 12; MaxTiles = 5; MaxLevel = 5; Title1 = " total best "; Title2 = " ------------ -------------------------------"; Title3 = "player games HH:MM score rows lvl date "; Title4 = "--------------- ----- ------ ------- ------ --- ----------------"; BlankLine = " "; TYPE EVT = { NONE, TICK, DROP, MOVE_LEFT, ROTATE_LEFT, ROTATE_RIGHT, MOVE_RIGHT }; Color = [0..36]; State = { FALLING, RESTING, BLINKING, PAUSING, DONE }; FullTileList = ARRAY [0..MaxTiles-1] OF Point.T; VAR games : ARRAY [0..3] OF Config.T; cur_game : INTEGER; cur_level : INTEGER; config : Config.T; VAR (* VBTs *) chassis : VBT.T; tsplitVBT : VBT.T; scoresVBT : VBT.T; scoreRows : ARRAY [0..MaxScores+1] OF VBT.T; gameVBT : VBT.T; imageV : VBT.T; scoreV : VBT.T; rowsV : VBT.T; header : VBT.T; stateV : VBT.T; gameV : VBT.T; gameTitle : VBT.T; handV : VBT.T; handTitle : VBT.T; levelV : VBT.T; levelTitle: VBT.T; keyLabelV : ARRAY [0..4] OF VBT.T; goV : VBT.T; goTitle : VBT.T; pauseV : VBT.T; bestV : VBT.T; quitV : VBT.T; domain : Rect.T; tints : ARRAY Color OF PaintOp.T; keymap : ARRAY [0..255] OF EVT; VBT_White : PaintOp.T; VBT_Black : PaintOp.T; clock : Thread.T; machine : Thread.T; oneHand : BOOLEAN := TRUE; focus : BOOLEAN := FALSE; running : BOOLEAN := FALSE; paused : BOOLEAN := FALSE; rowsDone : INTEGER; score : INTEGER; delay : Time.T; unit : INTEGER; northE : Rect.T; westE : Rect.T; southE : Rect.T; eastE : Rect.T; initDelay : Time.T; board : ARRAY [0..MaxRows-1], [0..MaxCols-1] OF Color; counter : INTEGER; curpiece : INTEGER; position : Point.T; rotation : INTEGER; curLoc : FullTileList; curColor : Color; curScored : BOOLEAN; random := NEW (Random.Default).init(); state := State.DONE; startTime : Time.T; stopTime : Time.T; pauseTime : Time.T; PROCEDURE Main Init () = VAR ignore: VBT.T; BEGIN (* build the colors *) VBT_White := PaintOp.FromRGB (1.0, 1.0, 1.0); VBT_Black := PaintOp.FromRGB (0.0, 0.0, 0.0); tints [ 0] := MakeColor (1.00, 1.00, 1.00); (* white *) tints [ 1] := MakeColor (1.00, 0.30, 0.00); (* orange *) tints [ 2] := MakeColor (1.00, 1.00, 0.00); (* yellow *) tints [ 3] := MakeColor (0.00, 0.00, 1.00); (* blue *) tints [ 4] := MakeColor (1.00, 0.00, 0.00); (* red *) tints [ 5] := MakeColor (0.00, 1.00, 0.00); (* green *) tints [ 6] := MakeColor (1.00, 0.00, 0.80); (* purplish red *) tints [ 7] := MakeColor (0.72, 0.66, 0.25); (* tan/khaki *) tints [ 8] := MakeColor (0.00, 1.00, 1.00); (* cyan *) tints [ 9] := MakeColor (0.00, 0.00, 0.25); (* navy blue *) (* tints [10] := MakeColor (0.69, 0.69, 0.14); (* goldenrod *) *) tints [10] := MakeColor (0.25, 0.20, 0.00); (* brown *) tints [11] := MakeColor (0.00, 0.59, 0.00); (* lime green *) tints [12] := MakeColor (0.37, 0.37, 0.37); (* light gray *) tints [13] := MakeColor (0.32, 0.10, 0.80); (* blue violet *) tints [14] := MakeColor (0.67, 0.67, 0.46); (* wheat *) tints [15] := MakeColor (0.50, 0.00, 0.52); (* light purple *) tints [16] := MakeColor (0.50, 0.00, 0.00); (* brick *) tints [17] := MakeColor (0.50, 0.09, 0.14); (* sick purple *) tints [18] := MakeColor (1.00, 0.70, 0.00); (* gold *) FOR i := 19 TO LAST(tints) DO tints [i] := MakeColor (random.real(), random.real(), random.real()); END; (* build the vbts *) imageV := NEW (GameVBT); scoreV := LabelVBT ("0 "); rowsV := LabelVBT ("0 "); stateV := LabelVBT ("READY "); keyLabelV[0] := LabelVBT (" "); keyLabelV[1] := LabelVBT (" "); keyLabelV[2] := LabelVBT (" "); keyLabelV[3] := LabelVBT (" "); keyLabelV[4] := LabelVBT (" "); gameV := NewButton ("Game: Fours ", GamePressed, gameTitle); levelV := NewButton ("Level: 1 ", LevelPressed, levelTitle); handV := NewButton ("Hands: one ", HandPressed, handTitle); goV := NewButton ("Go ", GoPressed, goTitle); pauseV := NewButton ("Pause", PausePressed, ignore); bestV := NewButton ("Scores", ScoresPressed, ignore); quitV := NewButton ("Exit", QuitPressed, ignore); header := VBTCol ( VBTRow (LabelVBT ("Score: "), scoreV), VBTRow (LabelVBT ("Rows: "), rowsV), VBTRow (LabelVBT ("State: "), stateV), Gap (40.0, 3.0), gameV, Gap (), levelV, Gap (), handV, Gap (), keyLabelV[0], keyLabelV[1], keyLabelV[2], keyLabelV[3], keyLabelV[4], LabelVBT (" "), goV, Gap (), pauseV, Gap (), bestV, Gap (), quitV ); gameVBT := HVSplit.Cons (Axis.T.Hor, VBTCol (Gap()), header, imageV); scoresVBT := HVSplit.New (Axis.T.Ver); Split.AddChild (scoresVBT, LabelVBT (Title1)); Split.AddChild (scoresVBT, LabelVBT (Title2)); Split.AddChild (scoresVBT, LabelVBT (Title3)); Split.AddChild (scoresVBT, LabelVBT (Title4)); FOR i := 0 TO LAST (scoreRows) DO scoreRows[i] := LabelVBT (BlankLine); Split.AddChild (scoresVBT, scoreRows[i]); END; Split.AddChild (scoresVBT, VBTRow (NewButton ("Ok", DonePressed, ignore))); Split.AddChild (scoresVBT, LabelVBT (" ")); tsplitVBT := TSplit.Cons (gameVBT, scoresVBT); chassis := tsplitVBT; (* select a game, key bindings & playing speed *) SetGame (2); SetKeyBindings (TRUE); SetLevel (1); Resize (Rect.Empty); InitQueue (); (* start the threads *) machine := Thread.Fork (NEW (Thread.Closure, apply := Machine)); clock := Thread.Fork (NEW (Thread.Closure, apply := Clock)); END Init; PROCEDUREGap (h, v: REAL := 2.0): VBT.T = BEGIN RETURN RigidVBT.FromHV (TextVBT.New (""), h, v); (* an h X v mm white space *) END Gap; PROCEDURENewButton (name: TEXT; proc: ButtonVBT.Proc; VAR label: VBT.T): VBT.T = BEGIN label := TextVBT.New (name); RETURN ButtonVBT.New (BorderedVBT.New (label), proc); END NewButton; VAR needFixed : BOOLEAN := TRUE; fixedFont : Font.T; PROCEDURELabelVBT (t: TEXT): VBT.T = BEGIN IF (needFixed) THEN needFixed := FALSE; fixedFont := Font.FromName ( ARRAY OF TEXT{"-*-courier-medium-r-*-*-*-120-*"}); END; RETURN TextVBT.New (t, 0.0, fnt := fixedFont); END LabelVBT; PROCEDURESetLabel (v: VBT.T; txt: TEXT) = BEGIN TextVBT.Put (v, txt); END SetLabel; PROCEDUREVBTRow (a0, a1, a2: VBT.T := NIL): VBT.T = BEGIN RETURN HVSplit.Cons (Axis.T.Hor, a0, a1, a2, TextVBT.New (" ")); END VBTRow; PROCEDUREVBTCol ( a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,a11,a12,a13,a14,a15,a16,a17,a18,a19, a20,a21,a22,a23 : VBT.T:=NIL): VBT.T = VAR v: VBT.T; BEGIN v := HVSplit.Cons (Axis.T.Ver, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); IF (a10 # NIL) THEN Split.AddChild (v, a10) END; IF (a11 # NIL) THEN Split.AddChild (v, a11) END; IF (a12 # NIL) THEN Split.AddChild (v, a12) END; IF (a13 # NIL) THEN Split.AddChild (v, a13) END; IF (a14 # NIL) THEN Split.AddChild (v, a14) END; IF (a15 # NIL) THEN Split.AddChild (v, a15) END; IF (a16 # NIL) THEN Split.AddChild (v, a16) END; IF (a17 # NIL) THEN Split.AddChild (v, a17) END; IF (a18 # NIL) THEN Split.AddChild (v, a18) END; IF (a19 # NIL) THEN Split.AddChild (v, a19) END; IF (a20 # NIL) THEN Split.AddChild (v, a20) END; IF (a21 # NIL) THEN Split.AddChild (v, a21) END; IF (a22 # NIL) THEN Split.AddChild (v, a22) END; IF (a23 # NIL) THEN Split.AddChild (v, a23) END; Split.AddChild (v, TextVBT.New (" ")); RETURN v; END VBTCol; PROCEDUREMakeColor (r, g, b: REAL): PaintOp.T = BEGIN RETURN PaintOp.Pair (VBT_White, PaintOp.FromRGB (r, g, b)); END MakeColor;
PROCEDURE--------------------------------------------------------------- threads ---ResetGame () = BEGIN (* init the scalars *) state := State.DONE; counter := 2; running := FALSE; paused := FALSE; score := 0; rowsDone := 0; delay := initDelay; (* start with an empty board *) FOR i := 0 TO MaxRows-1 DO FOR j := 0 TO MaxCols-1 DO board [i, j] := WHITE; END; END; (* move the currently "falling" piece off the visible board *) FOR i := 0 TO LAST (curLoc) DO curLoc[i].h := -10000; curLoc[i].v := -10000; END; (* and clear the board *) SetLabel (scoreV, "0"); SetLabel (rowsV, "0"); Resize (VBT.Domain (imageV)); VBT.ForceRepaint (imageV, Region.Full); END ResetGame;
PROCEDURE-------------------------------------------------------- game selection ---Clock (<*UNUSED*> arg: REFANY): REFANY = BEGIN LOOP Thread.Pause (delay); IF (running) AND (NOT paused) THEN PutEvent (EVT.TICK); END; END; END Clock; PROCEDUREMachine (<*UNUSED*> arg: REFANY): REFANY = VAR event: EVT; BEGIN (** wr := Wr.New (); ??? ***) LOOP event := GetEvent (); CASE event OF | EVT.TICK => CASE state OF | State.DONE => (* ignore *) | State.FALLING => IF NOT MoveDown () THEN state := State.RESTING; counter := 2 * VGrain; END; | State.RESTING => IF NOT MoveDown () THEN DEC (counter); IF (counter <= 0) THEN EndFall () END; ELSE state := State.FALLING; END; | State.BLINKING => DEC (counter); IF (counter <= 0) THEN RemoveSolidRows (); state := State.PAUSING; counter := 4 * VGrain; ELSIF ((counter MOD VGrain) = 0) THEN BlinkSolidRows (Word.And (counter, 1) = 1); END; | State.PAUSING => DEC (counter); IF (counter <= 0) THEN state := State.FALLING; StartNewPiece (); END; END; | EVT.DROP => IF (state = State.FALLING) THEN ScorePiece (); WHILE MoveDown () DO (* plummet *) END; counter := 1; END; | EVT.MOVE_LEFT => IF (state = State.FALLING) OR (state = State.RESTING) THEN MoveLeft (); END; | EVT.ROTATE_LEFT => IF (state = State.FALLING) OR (state = State.RESTING) THEN RotateLeft (); END; | EVT.ROTATE_RIGHT => IF (state = State.FALLING) OR (state = State.RESTING) THEN RotateRight (); END; | EVT.MOVE_RIGHT => IF (state = State.FALLING) OR (state = State.RESTING) THEN MoveRight () ; END; ELSE (* ignore *) END; END; END Machine; PROCEDUREEndFall () = VAR nSolid: INTEGER; BEGIN ScorePiece (); FixPiece (); nSolid := 0; FOR i := config.nRows-1 TO 0 BY -1 DO IF SolidRow (i) THEN INC (nSolid); ScoreRow (i) END; END; IF (nSolid > 0) THEN state := State.BLINKING; counter := 5 * VGrain; BlinkSolidRows (TRUE); ELSE state := State.PAUSING; counter := 4 * VGrain; END; END EndFall; PROCEDUREScorePiece () = BEGIN IF (NOT curScored) THEN ScorePoints ((config.nRows - (position.v DIV VGrain)) * HeightPts); curScored := TRUE; END; END ScorePiece; PROCEDUREScoreRow (row: INTEGER) = BEGIN ScorePoints (HeightPts * (config.nRows-row) * config.nCols * 2); INC (rowsDone); IF (rowsDone MOD SpeedUpRows) = 0 THEN delay := delay * SpeedUp; END; SetLabel (rowsV, Fmt.Int (rowsDone)); SetLabel (stateV, "RUNNING"); END ScoreRow; PROCEDUREScorePoints (pts: INTEGER) = BEGIN INC (score, TRUNC (FLOAT (pts, LONGREAL) * ScoreBasis / delay)); SetLabel (scoreV, Fmt.Int (score)); END ScorePoints; PROCEDUREEndGame () = BEGIN stopTime := Time.Now (); state := State.DONE; counter := 9999999; running := FALSE; paused := FALSE; UpdateScore (); SetLabel (stateV, "DONE"); SetLabel (goTitle, "Go "); END EndGame; PROCEDUREStartNewPiece () = VAR i, j, k: INTEGER; ok: BOOLEAN; BEGIN (* check for end of game *) FOR i := 0 TO config.nCols-1 DO IF (board [0, i] # WHITE) THEN (* the top row has a non-white cell *) EndGame (); RETURN; END; END; (* find a legal piece, position & rotation *) LOOP i := random.integer (0, config.nCols - 1); j := random.integer (0, config.nPieces - 1); k := random.integer (0, Config.NRotations - 1); WITH p = config.pieces[j][k] DO FOR z := 0 TO config.nTiles-1 DO curLoc [z].v := p.tiles[z].v - p.voffset; curLoc [z].h := p.tiles[z].h - p.hoffset + i; END; END; ok := TRUE; FOR z := 0 TO config.nTiles-1 DO ok := ok AND (0 <= curLoc[z].v) AND (curLoc[z].v < config.nRows) AND (0 <= curLoc[z].h) AND (curLoc[z].h < config.nCols) AND (board [curLoc[z].v, curLoc[z].h] = WHITE); END; IF ok THEN EXIT END; END; curpiece := j; rotation := k; WITH z = config.pieces[j][k] DO curColor := j+1; position := Point.FromCoords (i - z.hoffset, 0 - z.voffset * VGrain); END; FOR j := 0 TO config.nTiles-1 DO curLoc[j].v := curLoc[j].v * VGrain; PaintSquare (curLoc[j].v, curLoc[j].h, curColor); END; curScored := FALSE; END StartNewPiece; PROCEDUREBlinkSolidRows (on: BOOLEAN) = VAR r: Rect.T; BEGIN IF on THEN (* paint highlight *) r.west := domain.west; r.east := domain.east; FOR i := config.nRows-1 TO 0 BY -1 DO IF SolidRow (i) THEN r.north := domain.north + i * unit; r.south := r.north + unit; VBT.PaintTint (imageV, r, VBT_White); END; END; ELSE (* erase highlight *) FOR i := config.nRows-1 TO 0 BY -1 DO IF SolidRow (i) THEN FOR j := 0 TO config.nCols-1 DO PaintSquare (i * VGrain, j, board [i, j]); END; END; END; END; END BlinkSolidRows; PROCEDURERemoveSolidRows () = VAR k, delta: INTEGER; BEGIN delta := 0; FOR i := config.nRows-1 TO 0 BY -1 DO IF SolidRow (i) THEN INC (delta); FOR j := 0 TO config.nCols-1 DO IF (board [i, j] # WHITE) THEN board [i, j] := WHITE; PaintSquare (i * VGrain, j, WHITE); END; END; ELSIF (delta > 0) THEN k := i + delta; FOR j := 0 TO config.nCols-1 DO IF (board [k, j] # board [i,j]) THEN board [k, j] := board [i,j]; PaintSquare (k * VGrain, j, board [k, j]); END; END; FOR j := 0 TO config.nCols-1 DO IF (board [i, j] # WHITE) THEN board [i, j] := WHITE; PaintSquare (i * VGrain, j, WHITE); END; END; END; END; END RemoveSolidRows; PROCEDURESolidRow (r: INTEGER): BOOLEAN = BEGIN FOR i := 0 TO config.nCols-1 DO IF (board [r, i] = WHITE) THEN RETURN FALSE END; END; RETURN TRUE; END SolidRow; PROCEDUREFixPiece () = BEGIN FOR i := 0 TO config.nTiles-1 DO board [curLoc[i].v DIV VGrain, curLoc[i].h] := curColor; END; END FixPiece; PROCEDUREMoveDown (): BOOLEAN = BEGIN RETURN PlacePiece (rotation, position.v + 1, position.h); END MoveDown; PROCEDUREMoveLeft () = BEGIN IF PlacePiece (rotation, position.v, position.h - 1) THEN END; END MoveLeft; PROCEDUREMoveRight () = BEGIN IF PlacePiece (rotation, position.v, position.h + 1) THEN END; END MoveRight; PROCEDURERotateLeft () = BEGIN IF PlacePiece ((rotation + 1) MOD Config.NRotations, position.v, position.h) THEN END; END RotateLeft; PROCEDURERotateRight () = BEGIN IF PlacePiece ((rotation + Config.NRotations - 1) MOD Config.NRotations, position.v, position.h) THEN END; END RotateRight; TYPE RectList = ARRAY [0..MaxTiles-1] OF Rect.T; PROCEDUREPlacePiece (rot: INTEGER; row, col: INTEGER): BOOLEAN = VAR j, h, v: INTEGER; loc: FullTileList; old, new, toPaint, toErase: RectList; BEGIN (* map the piece *) FOR i := 0 TO config.nTiles-1 DO WITH p = config.pieces [curpiece][rot] DO loc[i].h := p.tiles[i].h + col; loc[i].v := p.tiles[i].v * VGrain + row; END; END; (* slide it back on the board *) h := config.nCols; v := 0; j := 0; FOR i := 0 TO config.nTiles-1 DO h := MIN (h, loc[i].h); j := MAX (j, loc[i].h); v := MIN (v, loc[i].v); END; IF (h < 0) THEN DEC (col, h); FOR i := 0 TO config.nTiles-1 DO DEC (loc[i].h, h) END; END; IF (j >= config.nCols) THEN j := j - config.nCols + 1; DEC (col, j); FOR i := 0 TO config.nTiles-1 DO DEC (loc[i].h, j) END; END; IF (v < 0) THEN DEC (row, v); FOR i := 0 TO config.nTiles-1 DO DEC (loc[i].v, v) END; END; (* test for a fit *) FOR i := 0 TO config.nTiles-1 DO h := loc[i].h; v := loc[i].v; j := v + (VGrain - 1); <* ASSERT (0 <= h) AND (h < config.nCols) AND (0 <= v) *> IF (config.nRows * VGrain <= j) THEN RETURN FALSE END; IF (board [j DIV VGrain, h] # WHITE) THEN RETURN FALSE END; IF (board [v DIV VGrain, h] # WHITE) THEN RETURN FALSE END; END; (* map the old and new squares *) MapSquares (curLoc, old); MapSquares (loc, new); (* find the paint lists *) SubtractRects (new, old, toPaint); SubtractRects (old, new, toErase); (* finally, paint the rectangles *) PaintRects (toErase, WHITE); PaintRects (toPaint, curColor); (* place the new piece *) position.v := row; position.h := col; rotation := rot; curLoc := loc; RETURN TRUE; END PlacePiece; PROCEDUREMapSquares (READONLY loc : FullTileList; VAR (*OUT*) rects : RectList) = BEGIN FOR i := 0 TO config.nTiles-1 DO WITH z = rects[i] DO z.north := domain.north + (loc[i].v * unit) DIV VGrain; z.west := domain.west + (loc[i].h * unit); z.south := z.north + unit; z.east := z.west + unit; END; END; END MapSquares; PROCEDURESubtractRects (READONLY a, b: RectList; VAR(*OUT*) c: RectList) = (* c := a - b *) VAR r: Rect.T; BEGIN FOR i := 0 TO config.nTiles-1 DO r := a[i]; FOR j := 0 TO config.nTiles-1 DO SubtractRect (r, b[j]); END; c[i] := r; END; END SubtractRects; PROCEDURESubtractRect (VAR a: Rect.T; READONLY b: Rect.T) = (* a := a - b *) BEGIN IF (a.north >= a.south) OR (a.west >= a.east) THEN (* a is empty *) RETURN; END; IF (a.west >= b.east) OR (b.west >= a.east) OR (a.north >= b.south) OR (b.north >= a.south) THEN (* no overlap *) RETURN; END; IF (a.west = b.west) AND (a.east = b.east) THEN IF (a.north >= b.north) AND (b.south >= a.south) THEN (* a is contained in b *) a.south := a.north; ELSIF (a.north >= b.north) THEN a.north := b.south; ELSIF (b.south >= a.south) THEN a.south := b.north; ELSE (* a.north < b.north AND b.south < a.south *) <* ASSERT FALSE *> END; ELSIF (a.north = b.north) AND (a.south = b.south) THEN IF (a.west >= b.west) AND (b.east >= a.east) THEN (* a is contained in b *) a.south := a.north; ELSIF (a.west >= b.west) THEN a.west := b.east; ELSIF (b.east >= a.east) THEN a.east := b.west; ELSE (* a.west < b.west AND b.east < a.east *) <* ASSERT FALSE *> END; ELSE <* ASSERT FALSE *> END; END SubtractRect; PROCEDUREPaintRects (READONLY x: RectList; color: Color) = BEGIN FOR i := 0 TO config.nTiles-1 DO WITH z = x[i] DO IF (z.north < z.south) AND (z.west < z.east) THEN VBT.PaintTint (imageV, z, tints [color]); END; END; END; END PaintRects; PROCEDUREPaintSquare (row, col: INTEGER; color: Color) = VAR r: Rect.T; BEGIN IF (NOT paused) THEN r.north := domain.north + (row * unit) DIV VGrain; r.west := domain.west + col * unit; r.south := r.north + unit; r.east := r.west + unit; VBT.PaintTint (imageV, r, tints [color]); END; END PaintSquare;
PROCEDURE---------------------------------------------------------- key bindings ---GamePressed (<*UNUSED*> v: ButtonVBT.T; <*UNUSED*> READONLY m: VBT.MouseRec) = BEGIN IF (NOT running) AND (state = State.DONE) THEN INC (cur_game); IF (cur_game >= NUMBER (games)) THEN cur_game := 0; END; SetGame (cur_game); END; END GamePressed; PROCEDURESetGame (id: INTEGER) = <*FATAL TrestleComm.Failure*> BEGIN cur_game := id; config := games [cur_game]; SetLabel (gameTitle, "Game: " & config.name); ResetScoreFileName (); Trestle.Decorate (chassis, config.name, config.name, config.name); <* ASSERT config.nPieces <= LAST(Color) *> ResetGame (); END SetGame;
PROCEDURE------------------------------------------------------------ game level ---HandPressed (<*UNUSED*> v: ButtonVBT.T; <*UNUSED*> READONLY m: VBT.MouseRec) = BEGIN IF (NOT running) AND (state = State.DONE) THEN oneHand := NOT oneHand; SetKeyBindings (oneHand); END; END HandPressed; PROCEDURESetKeyBindings (on: BOOLEAN) = BEGIN (* record the global state *) oneHand := on; (* build the key mapping *) FOR k := FIRST (keymap) TO LAST (keymap) DO keymap [k] := EVT.NONE END; keymap [Latin1Key.space] := EVT.DROP; IF (oneHand) THEN keymap [Latin1Key.S] := EVT.MOVE_LEFT; keymap [Latin1Key.s] := EVT.MOVE_LEFT; keymap [Latin1Key.D] := EVT.ROTATE_LEFT; keymap [Latin1Key.d] := EVT.ROTATE_LEFT; keymap [Latin1Key.F] := EVT.MOVE_RIGHT; keymap [Latin1Key.f] := EVT.MOVE_RIGHT; keymap [Latin1Key.J] := EVT.MOVE_LEFT; keymap [Latin1Key.j] := EVT.MOVE_LEFT; keymap [Latin1Key.K] := EVT.ROTATE_LEFT; keymap [Latin1Key.k] := EVT.ROTATE_LEFT; keymap [Latin1Key.L] := EVT.MOVE_RIGHT; keymap [Latin1Key.l] := EVT.MOVE_RIGHT; ELSE keymap [Latin1Key.D] := EVT.MOVE_LEFT; keymap [Latin1Key.d] := EVT.MOVE_LEFT; keymap [Latin1Key.F] := EVT.ROTATE_LEFT; keymap [Latin1Key.f] := EVT.ROTATE_LEFT; keymap [Latin1Key.J] := EVT.ROTATE_RIGHT; keymap [Latin1Key.j] := EVT.ROTATE_RIGHT; keymap [Latin1Key.K] := EVT.MOVE_RIGHT; keymap [Latin1Key.k] := EVT.MOVE_RIGHT; END; (* set the window labels *) IF (oneHand) THEN SetLabel (keyLabelV[0], "s, j - move left "); SetLabel (keyLabelV[1], "d, k - rotate "); SetLabel (keyLabelV[2], "f, l - move right"); SetLabel (keyLabelV[3], "<space> - drop "); SetLabel (keyLabelV[4], " "); ELSE SetLabel (keyLabelV[0], "d - move left "); SetLabel (keyLabelV[1], "f - rotate down "); SetLabel (keyLabelV[2], "j - rotate up "); SetLabel (keyLabelV[3], "k - move right "); SetLabel (keyLabelV[4], "<space> - drop "); END; (* reset the button *) IF (oneHand) THEN SetLabel (handTitle, "Hands: one"); ELSE SetLabel (handTitle, "Hands: two"); END; END SetKeyBindings;
PROCEDURE---------------------------------------------------------- misc control ---LevelPressed (<*UNUSED*> v: ButtonVBT.T; <*UNUSED*> READONLY m: VBT.MouseRec) = BEGIN IF (NOT running) AND (state = State.DONE) THEN cur_level := cur_level + 1; IF (cur_level > MaxLevel) THEN cur_level := 1; END; SetLevel (cur_level); END; END LevelPressed; PROCEDURESetLevel (lev: INTEGER) = BEGIN cur_level := MIN (MAX (1, lev), MaxLevel); initDelay := config.delay * (1.0d0 - 0.15d0 * FLOAT(cur_level, LONGREAL)); SetLabel (levelTitle, "Level: " & Fmt.Int (cur_level)); END SetLevel;
PROCEDURE---------------------------------------------------------------- scores ---GoPressed (<*UNUSED*> v: ButtonVBT.T; READONLY m: VBT.MouseRec) = BEGIN IF (state = State.DONE) AND (NOT running) THEN ResetGame (); state := State.PAUSING; counter := 2; running := TRUE; SetLabel (stateV, "RUNNING"); SetLabel (goTitle, "Stop"); startTime := Time.Now (); ELSIF (paused) THEN startTime := startTime + (Time.Now () - pauseTime); paused := FALSE; SetLabel (stateV, "RUNNING"); PaintGame (imageV, Region.Full); PutEvent (EVT.DROP); ELSIF (running) THEN EndGame (); END; GetFocus (m.time); END GoPressed; PROCEDUREPausePressed (<*UNUSED*> v: ButtonVBT.T; READONLY m: VBT.MouseRec) = BEGIN IF (running) AND (NOT paused) THEN pauseTime := Time.Now (); paused := TRUE; SetLabel (stateV, "PAUSED"); PaintGame (imageV, Region.Full); SetLabel (goTitle, "Resume"); END; GetFocus (m.time); END PausePressed; PROCEDUREDonePressed (<*UNUSED*> v: ButtonVBT.T; <*UNUSED*> READONLY m: VBT.MouseRec) = <*FATAL Split.NotAChild*> BEGIN TSplit.SetCurrent (tsplitVBT, gameVBT); VBT.ForceRepaint (tsplitVBT, Region.Full); END DonePressed; PROCEDUREQuitPressed (<*UNUSED*> v: ButtonVBT.T; <*UNUSED*> READONLY m: VBT.MouseRec) = BEGIN Process.Exit (0); END QuitPressed;
TYPE Result = REF RECORD next : Result; player : TEXT; score : ScoreFile.Score; END; VAR scoreFile : TEXT := ""; dumping : BOOLEAN := FALSE; PROCEDURE-------------------------------------------------------- game board VBT ---UpdateScore () = VAR s: ScoreFile.Score; BEGIN s.n_games := 1; s.n_seconds := stopTime - startTime; s.best_date := Time.Now (); s.best_level := cur_level; s.best_rows := rowsDone; s.best_score := score; TRY ScoreFile.Put (scoreFile, PlayerName (), s); EXCEPT ScoreFile.Error(msg) => NoteScoreFileError ("update", msg); END; END UpdateScore; PROCEDUREScoresPressed (<*UNUSED*> v: ButtonVBT.T; <*UNUSED*> READONLY m: VBT.MouseRec) = <*FATAL Split.NotAChild*> VAR j := 0; wr := TextWr.New (); r := GetResults (); me := PlayerName (); zz : Result := NIL; BEGIN FOR i := 0 TO MaxScores-1 DO IF (r = NIL) THEN EXIT END; PrintResult (wr, r); SetLabel (scoreRows[j], TextWr.ToText (wr)); INC (j); IF (zz = NIL) AND Text.Equal (r.player, me) THEN zz := r END; r := r.next; END; (* see if we can print my score *) IF (zz = NIL) THEN WHILE (r # NIL) DO IF Text.Equal (r.player, me) THEN PrintResult (wr, r); SetLabel (scoreRows[j], BlankLine); INC (j); SetLabel (scoreRows[j], TextWr.ToText (wr)); INC (j); END; END; END; FOR i := j TO LAST (scoreRows) DO SetLabel (scoreRows[i], BlankLine); END; (* and show it *) TSplit.SetCurrent (tsplitVBT, scoresVBT); VBT.ForceRepaint (tsplitVBT, Region.Full); END ScoresPressed; PROCEDUREDumpScoreFiles () = <*FATAL Thread.Alerted, Wr.Failure*> VAR wr := Stdio.stdout; BEGIN dumping := TRUE; FOR i := FIRST (games) TO LAST (games) DO config := games[i]; ResetScoreFileName (); IF ScoresExist () THEN Wr.PutText (wr, Wr.EOL & "------ "); Wr.PutText (wr, config.name); Wr.PutText (wr, " ------" & Wr.EOL); DumpScoreFile (); END; END; Wr.Flush (wr); END DumpScoreFiles; PROCEDUREScoresExist (): BOOLEAN = BEGIN TRY EVAL FS.Status (scoreFile); RETURN TRUE; EXCEPT OSError.E => RETURN FALSE; END; END ScoresExist; PROCEDUREDumpScoreFile () = <*FATAL Thread.Alerted, Wr.Failure*> VAR r := GetResults (); wr := Stdio.stdout; BEGIN Wr.PutText (wr, Title1); Wr.PutText (wr, Wr.EOL); Wr.PutText (wr, Title2); Wr.PutText (wr, Wr.EOL); Wr.PutText (wr, Title3); Wr.PutText (wr, Wr.EOL); Wr.PutText (wr, Title4); Wr.PutText (wr, Wr.EOL); WHILE (r # NIL) DO PrintResult (wr, r); Wr.PutText (wr, Wr.EOL); r := r.next; END; Wr.PutText (wr, Wr.EOL); Wr.Flush (wr); END DumpScoreFile; PROCEDUREGetResults (): Result = VAR n_results := 0; all_results : Result; PROCEDURE NoteScore (p: ScoreFile.Player; READONLY s: ScoreFile.Score) = VAR BEGIN all_results := NEW(Result, next := all_results, player := p, score := s); INC (n_results); END NoteScore; BEGIN TRY ScoreFile.Enumerate (scoreFile, NoteScore); EXCEPT ScoreFile.Error(msg) => NoteScoreFileError ("read", msg); END; RETURN SortResults (all_results, n_results); END GetResults; PROCEDURESortResults (r: Result; cnt: INTEGER): Result = VAR map := NEW (REF ARRAY OF INTEGER, cnt); ref := NEW (REF ARRAY OF Result, cnt); x : Result; PROCEDURE CmpResult (a, b: INTEGER): [-1..+1] = VAR xa := ref[a]; xb := ref[b]; BEGIN IF (xa.score.best_score > xb.score.best_score) THEN RETURN -1; ELSIF (xa.score.best_score < xb.score.best_score) THEN RETURN +1; ELSE RETURN Text.Compare (xa.player, xb.player); END; END CmpResult; BEGIN FOR i := 0 TO cnt-1 DO ref [i] := r; r := r.next; map [i] := i; END; (* sort them *) IntArraySort.Sort (map^, CmpResult); (* rebuild the linked list *) r := NIL; FOR i := cnt-1 TO 0 BY -1 DO x := ref [map [i]]; x.next := r; r := x; END; RETURN r; END SortResults; PROCEDUREPrintResult (wr: Wr.T; r: Result) = <*FATAL Wr.Failure, Thread.Alerted*> VAR xx : ARRAY [0..11] OF TEXT; minutes := ROUND ((r.score.n_seconds + 30.0d0) / 60.0d0); hours := minutes DIV 60; date := Date.FromTime (r.score.best_date); BEGIN minutes := minutes - hours * 60; xx[0] := Text.Sub (r.player, 0, 16); xx[1] := Fmt.Int (r.score.n_games); xx[2] := Fmt.Int (hours); xx[3] := Fmt.Int (minutes); xx[4] := Fmt.Int (r.score.best_score); xx[5] := Fmt.Int (r.score.best_rows); xx[6] := Fmt.Int (r.score.best_level); xx[7] := Fmt.Int (date.year MOD 100); xx[8] := Fmt.Int (ORD (date.month) + 1); xx[9] := Fmt.Int (date.day); xx[10] := Fmt.Int (date.hour); xx[11] := Fmt.Int (date.minute); Wr.PutText (wr, Fmt.FN("%-16s %4s %3s:%02s %7s %6s %s %02s.%02s.%02s %2s:%02s", xx)); END PrintResult; PROCEDUREPlayerName (): TEXT = VAR n: TEXT; BEGIN n := Env.Get (ALIAS); IF (n # NIL) THEN RETURN n END; n := OSConfig.UserName (); IF (n # NIL) THEN RETURN n END; RETURN "<unknown player>"; END PlayerName; PROCEDUREResetScoreFileName () = BEGIN scoreFile := ScoreDir.Root & config.name & ".scores"; END ResetScoreFileName; PROCEDURENoteScoreFileError (op, msg: TEXT) = <*FATAL ANY*> BEGIN IF (dumping) THEN IO.Put ("** unable to " & op & " score file \"" & scoreFile & "\""); IO.Put ("** " & msg); ELSE (* clear the score file display *) FOR i := FIRST (scoreRows) TO LAST (scoreRows) DO SetLabel (scoreRows [i], BlankLine); END; (* insert the error message *) SetLabel (scoreRows [2], "** unable to " & op & " score file \"" & scoreFile & "\""); SetLabel (scoreRows [3], "** " & msg); (* and show it *) TSplit.SetCurrent (tsplitVBT, scoresVBT); VBT.ForceRepaint (tsplitVBT, Region.Full); END; END NoteScoreFileError;
TYPE GameVBT = VBT.Leaf OBJECT OVERRIDES mouse := MouseGame; key := GameKey; reshape := ReshapeGame; repaint := PaintGame; shape := GameShape; misc := GameMisc; END; PROCEDURE-------------------------------------------------------- keyboard focus ---MouseGame (<*UNUSED*> v: GameVBT; READONLY cd: VBT.MouseRec) = BEGIN IF (cd.clickType = VBT.ClickType.FirstDown) THEN GetFocus (cd.time); IF (cd.whatChanged = VBT.Modifier.MouseL) THEN PutEvent (EVT.MOVE_LEFT); ELSIF (cd.whatChanged = VBT.Modifier.MouseM) THEN PutEvent (EVT.ROTATE_LEFT); ELSIF (cd.whatChanged = VBT.Modifier.MouseR) THEN PutEvent (EVT.MOVE_RIGHT); END; END; END MouseGame; PROCEDUREGameKey (<*UNUSED*> v: GameVBT; READONLY cd: VBT.KeyRec) = VAR e: EVT; BEGIN IF (cd.wentDown) THEN e := keymap [Word.And (cd.whatChanged, 16_ff)]; IF (e # EVT.NONE) THEN PutEvent (e); END; END; END GameKey; PROCEDUREGameShape (<*UNUSED*> v : GameVBT; ax : Axis.T; <*UNUSED*> n : CARDINAL): VBT.SizeRange = VAR sz: INTEGER; BEGIN IF (ax = Axis.T.Hor) THEN sz := config.nCols; ELSE sz := config.nRows; END; sz := Margin + sz * VGrain * MinCell; RETURN VBT.SizeRange {lo := sz, pref := sz, hi := 1024}; END GameShape; PROCEDUREGameMisc (<*UNUSED*> v: VBT.T; READONLY cd: VBT.MiscRec) = BEGIN IF (cd.type = VBT.Deleted) OR (cd.type = VBT.Lost) (* OR (cd.type = VBT.Iconized) *) THEN DropFocus (); (* ELSIF (cd.type = VBT.Deiconized) THEN GetFocus (cd.time); *) END; END GameMisc; PROCEDUREPaintGame (v: GameVBT; READONLY rgn: Region.T) = VAR s: Rect.T; r := Region.BoundingBox (rgn); BEGIN VBT.PaintTint (v, r, VBT_White); VBT.PaintTint (v, Rect.Meet (r, northE), VBT_Black); VBT.PaintTint (v, Rect.Meet (r, eastE), VBT_Black); VBT.PaintTint (v, Rect.Meet (r, southE), VBT_Black); VBT.PaintTint (v, Rect.Meet (r, westE), VBT_Black); IF (paused) THEN VBT.PaintTint (v, Rect.Meet (r, domain), VBT_White); RETURN; END; FOR i := 0 TO config.nRows-1 DO s.north := domain.north + i * unit; s.south := s.north + unit; FOR j := 0 TO config.nCols-1 DO s.west := domain.west + j * unit; s.east := s.west + unit; IF (board[i,j] # WHITE) THEN VBT.PaintTint (v, Rect.Meet (r, s), tints [board[i,j]]); END; END; END; IF (running) THEN FOR i := 0 TO config.nTiles-1 DO PaintSquare (curLoc[i].v, curLoc[i].h, curColor); END; END; END PaintGame; PROCEDUREReshapeGame (v: GameVBT; READONLY cd: VBT.ReshapeRec) = BEGIN Resize (cd.new); PaintGame (v, Region.Full); END ReshapeGame; PROCEDUREResize (READONLY r: Rect.T) = CONST BorderWidth = 2; VAR h, v, s: INTEGER; p: Point.T; BEGIN h := (Rect.HorSize (r) - Margin) DIV (config.nCols * VGrain); v := (Rect.VerSize (r) - Margin) DIV (config.nRows * VGrain); s := MAX (MinCell, MIN (h, v)) * VGrain; p := Rect.Middle (r); unit := s; domain.north := p.v - (config.nRows * s) DIV 2; domain.west := p.h - (config.nCols * s) DIV 2; domain.south := domain.north + config.nRows * s; domain.east := domain.west + config.nCols * s; northE.north := domain.north - BorderWidth; northE.south := domain.north; northE.west := domain.west - BorderWidth; northE.east := domain.east + BorderWidth; southE.north := domain.south; southE.south := domain.south + BorderWidth; southE.west := domain.west - BorderWidth; southE.east := domain.east + BorderWidth; westE.north := domain.north; westE.south := domain.south; westE.west := domain.west - BorderWidth; westE.east := domain.west; eastE.north := domain.north; eastE.south := domain.south; eastE.west := domain.east; eastE.east := domain.east + BorderWidth; END Resize;
PROCEDURE----------------------------------------------------------- event queue ---GetFocus (t: VBT.TimeStamp) = BEGIN IF (NOT focus) THEN TRY VBT.Acquire (imageV, VBT.KBFocus, t); focus := TRUE; EXCEPT VBT.Error => END; END; END GetFocus; PROCEDUREDropFocus () = BEGIN IF (focus) THEN VBT.Release (imageV, VBT.KBFocus); focus := FALSE; END; END DropFocus;
VAR events: RECORD mutex : MUTEX; cnt : INTEGER; head : INTEGER; tail : INTEGER; nonempty : Thread.Condition; nonfull : Thread.Condition; contents : ARRAY [0..2] OF EVT; END; PROCEDURE---------------------------------------------------------- main program ---InitQueue () = BEGIN events.cnt := 0; events.head := 0; events.tail := 0; events.mutex := NEW (MUTEX); events.nonfull := NEW (Thread.Condition); events.nonempty := NEW (Thread.Condition); END InitQueue; PROCEDUREPutEvent (evt: EVT) = BEGIN WITH z = events DO LOCK z.mutex DO WHILE (z.cnt >= NUMBER (z.contents)) DO Thread.Wait (z.mutex, z.nonfull); END; z.contents [z.head] := evt; INC (z.head); IF (z.head >= NUMBER (z.contents)) THEN z.head := 0; END; INC (z.cnt); END; Thread.Signal (z.nonempty); END; END PutEvent; PROCEDUREGetEvent (): EVT = VAR evt: EVT; BEGIN WITH z = events DO LOCK z.mutex DO WHILE (z.cnt <= 0) DO Thread.Wait (z.mutex, z.nonempty); END; evt := z.contents [z.tail]; INC (z.tail); IF (z.tail >= NUMBER (z.contents)) THEN z.tail := 0; END; DEC (z.cnt); END; Thread.Signal (z.nonfull); END; RETURN evt; END GetEvent;
PROCEDUREDoIt () = <*FATAL TrestleComm.Failure*> VAR i: INTEGER; arg: TEXT; BEGIN (* build the game descriptions *) games[0] := Config.New (2); games[1] := Config.New (3); games[2] := Config.New (4); games[3] := Config.New (5); (* check the command line options *) i := 1; WHILE (i < Params.Count) DO arg := Params.Get (i); IF Text.Equal (arg, "-scores") THEN DumpScoreFiles (); RETURN; ELSE IO.Put ("Unrecognized option: \"" & arg & "\", ignored" & Wr.EOL); END; INC (i); END; Init (); ResetGame (); Trestle.Install (chassis, config.name, config.name, config.name); Trestle.AwaitDelete (chassis); END DoIt; BEGIN DoIt (); END Main.