MODULE VNCTetrisServer;
IMPORT Raster, KernelLog, Commands, Kernel, Random, VNCServer, WMRectangles,
WMGraphics, Inputs, Modules, Files, IP, Dates, Strings;
CONST
Width = 10;
Height = 30;
BoxSize = 16;
BlockSize = 5;
NofBlocks = 7;
ScoreHeight = 108;
DataFile = "VNCTetris.dat";
VAR colors : ARRAY NofBlocks + 1 OF Raster.Pixel;
gamesRunning, gamesTotal, maxConcurrent, highScore : LONGINT;
shuttingDown : BOOLEAN;
server: VNCServer.Server;
TYPE
Block = ARRAY BlockSize, BlockSize OF CHAR;
TimeDate = RECORD h, m, s, day,month,year: LONGINT END;
TT = OBJECT
VAR
alive, dropped : BOOLEAN;
field : ARRAY Width OF ARRAY Height OF CHAR;
block : Block;
posX, posY : LONGINT;
timer : Kernel.Timer;
mode : Raster.Mode;
random : Random.Generator;
lines, blocks, delay, delayDec: LONGINT;
img : Raster.Image;
paused, run: BOOLEAN;
cheated: LONGINT;
time : TimeDate;
adrStr, timeStr:ARRAY 32 OF CHAR;
vncInfo : VNCServer.VNCInfo;
canvas : WMGraphics.BufferCanvas;
PROCEDURE &Create*(vncInfo : VNCServer.VNCInfo);
BEGIN
NEW(img);
Raster.Create(img, Width * BoxSize, Height * BoxSize + ScoreHeight, Raster.BGR565);
Raster.InitMode(mode, Raster.srcCopy); NEW(timer); NEW(random); random.InitSeed(Kernel.GetTicks()); lines := 0;
NEW(canvas, img);
SELF.vncInfo := vncInfo;
run := FALSE
END Create;
PROCEDURE Run;
BEGIN {EXCLUSIVE}
run := TRUE
END Run;
PROCEDURE Bound(VAR x:LONGINT; min, max:LONGINT);
BEGIN IF x<min THEN x:=min ELSE IF x>max THEN x:=max END END
END Bound;
PROCEDURE ClipAtImage(VAR x: WMRectangles.Rectangle; img:Raster.Image);
BEGIN
Bound(x.l, 0, img.width);Bound(x.r, 0, img.width);
Bound(x.t, 0, img.height);Bound(x.b, 0, img.height)
END ClipAtImage;
PROCEDURE AddDirty(l, t, r, b:LONGINT);
VAR x: WMRectangles.Rectangle;
BEGIN
WMRectangles.SetRect(x, l, t, r, b);
ClipAtImage(x, img);
IF vncInfo.agent # NIL THEN vncInfo.agent.AddDirty(x) END
END AddDirty;
PROCEDURE Close;
BEGIN
alive := FALSE
END Close;
PROCEDURE RotateBlock(VAR block:Block):Block;
VAR i, j : INTEGER; temp : Block;
BEGIN
FOR i := 0 TO BlockSize - 1 DO FOR j := 0 TO BlockSize - 1 DO temp[j, i] := block[(BlockSize - 1) - i, j] END END;
RETURN temp
END RotateBlock;
PROCEDURE DrawBox(x, y: LONGINT; color: CHAR);
VAR pix : Raster.Pixel;
BEGIN
pix := colors [ORD(color)];
IF (x >= 0) & (x < Width) & (y >= 0) & (y < Height) THEN
Raster.Fill(img, x * BoxSize, y * BoxSize, x * BoxSize+ BoxSize, y * BoxSize + BoxSize, pix, mode)
END;
END DrawBox;
PROCEDURE SetBlock(x, y : LONGINT; clear : BOOLEAN);
VAR i, j : LONGINT;
BEGIN
FOR i := 0 TO BlockSize - 1 DO FOR j := 0 TO BlockSize - 1 DO
IF block[i, j] # 0X THEN
IF (i + x < Width) & (j + y >= 0) & (j + y < Height) THEN
IF clear THEN
field[i + x, j + y] := 0X;
DrawBox(i + x, j + y, 0X)
ELSE field[i + x, j + y] := block[i, j];
DrawBox(i + x, j + y, block[i, j])
END
END
END
END END
END SetBlock;
PROCEDURE HasDownCollision(x, y: LONGINT) : BOOLEAN;
VAR i, j : LONGINT;
BEGIN
FOR i := 0 TO BlockSize - 1 DO FOR j := 0 TO BlockSize - 1 DO
IF block[i, j] # 0X THEN
IF (i + x < Width) & (j + y >= 0) THEN
IF (j + y < Height) THEN
IF (block[i, j] # 0X) & (field[i + x, j + y] # 0X) THEN RETURN TRUE END
ELSIF block[i, j] # 0X THEN RETURN TRUE
END
ELSE RETURN TRUE
END
END
END END;
RETURN FALSE
END HasDownCollision;
PROCEDURE HasCollision(VAR bl: Block; x, y: LONGINT) : BOOLEAN;
VAR i, j : LONGINT;
BEGIN
FOR i := 0 TO BlockSize - 1 DO FOR j := 0 TO BlockSize - 1 DO
IF bl[i, j] # 0X THEN
IF (i + x >= Width) OR (i + x < 0) OR (j + y >= Height) OR (field[i + x, j + y] # 0X) THEN RETURN TRUE END
END
END END;
RETURN FALSE
END HasCollision;
PROCEDURE Move(dir: LONGINT):BOOLEAN;
VAR newX, newY: LONGINT; result : BOOLEAN;
BEGIN
newX := posX; newY := posY;
IF dir = 0 THEN INC(newX)
ELSIF dir = 1 THEN DEC(newX)
ELSIF dir = 2 THEN INC(newY)
END;
SetBlock(posX, posY, TRUE);
IF ~HasCollision(block, newX, newY) THEN posX := newX; posY := newY; result := TRUE
ELSE result := FALSE
END;
SetBlock(posX, posY, FALSE);
AddDirty(posX * BoxSize - BoxSize, posY * BoxSize - BoxSize, posX * BoxSize + BlockSize * BoxSize + BoxSize,
posY * BoxSize + BlockSize*BoxSize +BoxSize);
RETURN result
END Move;
PROCEDURE KeyPressed(ucs : LONGINT; flags: SET; keysym: LONGINT);
VAR ignore : BOOLEAN;
rotBlock : Block;
BEGIN {EXCLUSIVE}
IF ~alive THEN RETURN END;
IF Inputs.Release IN flags THEN RETURN END;
IF (ucs = ORD("p")) OR (ucs = ORD("P")) THEN paused := ~paused END;
IF (keysym = 0FF50H) OR (keysym = 0FF51H) THEN ignore := Move(1); IF paused THEN INC(cheated) END
ELSIF (keysym = 0FF55H)OR (keysym = 0FF53H) THEN ignore := Move(0); IF paused THEN INC(cheated) END
ELSIF (keysym = 0FF52H) OR (keysym = 0FF09H) THEN
SetBlock(posX, posY, TRUE);
rotBlock := RotateBlock(block);
IF ~HasCollision(rotBlock, posX, posY) THEN block := rotBlock END;
SetBlock(posX, posY, FALSE);
AddDirty(posX * BoxSize - BoxSize, posY * BoxSize - BoxSize, posX * BoxSize + BlockSize * BoxSize,
posY * BoxSize + BlockSize * BoxSize);
IF paused THEN INC(cheated) END
ELSIF (keysym = 0FF54H) OR (keysym = 0FF0DH) OR (keysym = 20H) THEN
dropped := TRUE; IF paused THEN INC(cheated) END
END
END KeyPressed;
PROCEDURE NewBlock;
VAR i, j: LONGINT; kind : LONGINT;
color : CHAR;
PROCEDURE Set(x, y: LONGINT);
BEGIN block[x, y] := color
END Set;
BEGIN
UpdateScore(FALSE);
dropped := FALSE;
posX := Width DIV 2 - 1; posY := 0;
FOR i := 0 TO BlockSize - 1 DO FOR j := 0 TO BlockSize - 1 DO block [i, j] := 0X END END;
kind := random.Integer() MOD NofBlocks;
color := CHR(1 + kind);
CASE kind OF
0 : Set(0, 2); Set(1, 2); Set(2, 2); Set(3, 2)
|1 : Set(1, 3); Set(2, 3); Set(3, 3); Set(2, 2)
|2 : Set(1, 1); Set(1, 2); Set(2, 2); Set(2, 3)
|3 : Set(2, 1); Set(1, 2); Set(2, 2); Set(1, 3)
|4 : Set(2, 1); Set(2, 2); Set(2, 3); Set(3, 3)
|5 : Set(2, 1); Set(2, 2); Set(2, 3); Set(1, 3)
|6 : Set(1, 1); Set(1, 2); Set(2, 1); Set(2, 2)
END;
INC(blocks);
IF HasCollision(block, posX, posY) THEN alive := FALSE; END
END NewBlock;
PROCEDURE RemoveLine(y: LONGINT);
VAR i, j: LONGINT;
BEGIN
FOR i := 0 TO Width - 1 DO
FOR j := y TO 1 BY - 1 DO
field[i, j] := field[i, j - 1];
DrawBox(i, j, field[i, j])
END;
field[i, 0] := 0X;
DrawBox(i, 0, 0X)
END;
AddDirty(0, 0, Width * BoxSize, y * BoxSize + BoxSize);
INC(lines);
timer.Sleep(200);
IF delay > 10 THEN DEC(delay, delayDec) END;
IF delayDec >= 4 THEN delayDec := delayDec * 2 DIV 3 END
END RemoveLine;
PROCEDURE ClearLines;
VAR y, x, c: LONGINT;
BEGIN
y := Height - 1;
WHILE y > 0 DO
c := 0; FOR x := 0 TO Width - 1 DO IF field[x, y] # 0X THEN INC(c) END END;
IF c = Width THEN RemoveLine(y) ELSE DEC(y) END
END
END ClearLines;
PROCEDURE DropStep;
VAR needNew: BOOLEAN;
BEGIN {EXCLUSIVE}
AWAIT(~paused);
SetBlock(posX, posY, TRUE);
IF ~HasDownCollision(posX, posY +1) THEN INC(posY); needNew := FALSE ELSE needNew := TRUE END;
SetBlock(posX, posY, FALSE);
AddDirty(posX * BoxSize - BoxSize, posY * BoxSize - BoxSize, posX * BoxSize + BlockSize * BoxSize,
posY * BoxSize + BlockSize*BoxSize);
IF needNew THEN
ClearLines;
NewBlock
END
END DropStep;
PROCEDURE UpdateScore(eog: BOOLEAN);
VAR pix : Raster.Pixel; str : ARRAY 16 OF CHAR; ypos : LONGINT;
BEGIN
Raster.SetRGB(pix, 255, 255, 255);
Raster.Fill(img, 0, Height * BoxSize, Width * BoxSize, Height * BoxSize + ScoreHeight, pix, mode);
Strings.IntToStr(lines*10+blocks, str);
ypos := Height * BoxSize +13;
canvas.DrawString(0, ypos, "Score:"); canvas.DrawString(100, ypos, str); INC(ypos, 13);
Strings.IntToStr(GetGamesRunning(), str);
canvas.DrawString(0, ypos, "Games active:"); canvas.DrawString(100, ypos, str); INC(ypos, 13);
Strings.IntToStr(GetMaxConcurrent(), str);
canvas.DrawString(0, ypos, "Max concurrent:"); canvas.DrawString(100, ypos, str); INC(ypos, 13);
Strings.IntToStr(GetGamesTotal(), str);
canvas.DrawString(0, ypos, "Served total:"); canvas.DrawString(100, ypos, str);INC(ypos, 13);
Strings.IntToStr(GetHighscore(), str);
canvas.DrawString(0, ypos, "High score:"); canvas.DrawString(100, ypos, str);INC(ypos, 13);
canvas.DrawString(0, ypos, "Press p to toggle pause"); INC(ypos, 13);
IF GetIsShuttingDown() THEN
canvas.DrawString(0, ypos, "THE SERVER IS SHUTTING DOWN"); INC(ypos, 13)
END;
IF (cheated >= 5) & eog THEN
canvas.DrawString(0, ypos, "Phuking cheater !"); INC(ypos, 13)
END;
AddDirty(0, Height * BoxSize, Width * BoxSize, Height * BoxSize + ScoreHeight);
END UpdateScore;
PROCEDURE AwaitRun;
BEGIN {EXCLUSIVE}
AWAIT(run)
END AwaitRun;
BEGIN {ACTIVE}
AwaitRun;
IP.AdrToStr(vncInfo.connection.fip, adrStr);
KernelLog.Enter;
Strings.DateToStr(Dates.Now(), timeStr); KernelLog.String(timeStr); KernelLog.String(" ");
Strings.TimeToStr(Dates.Now(), timeStr); KernelLog.String(timeStr);
KernelLog.String(" IP: "); KernelLog.String(adrStr);
KernelLog.String(":"); KernelLog.Int(vncInfo.connection.fport, 5);
KernelLog.String(" started");
KernelLog.Exit;
AddGame;
alive := ~GetIsShuttingDown(); delay :=300; delayDec := 20;
NewBlock;
WHILE alive DO
IF ~dropped THEN timer.Sleep(delay) END;
DropStep
END;
KernelLog.Enter;
Strings.DateToStr(Dates.Now(), timeStr); KernelLog.String(timeStr); KernelLog.String(" ");
Strings.TimeToStr(Dates.Now(), timeStr); KernelLog.String(timeStr);
KernelLog.String(" IP: "); KernelLog.String(adrStr);
KernelLog.String(":"); KernelLog.Int(vncInfo.connection.fport, 5);
KernelLog.String(" Score: "); KernelLog.Int(lines * 10 + blocks, 1);
IF (cheated > 0) THEN KernelLog.String(" (cheated "); KernelLog.Int(cheated, 0); KernelLog.String(" times)") END;
KernelLog.Exit;
SubGame;
IF (cheated < 5) THEN ReportScore(lines * 10 + blocks) END;
UpdateScore(TRUE)
END TT;
PROCEDURE StartTT(vncInfo : VNCServer.VNCInfo);
VAR t: TT;
BEGIN
NEW(t, vncInfo);
vncInfo.img := t.img;
vncInfo.kl := t.KeyPressed;
t.Run
END StartTT;
PROCEDURE AddGame;
BEGIN {EXCLUSIVE}
INC(gamesTotal);
INC(gamesRunning);
maxConcurrent := Max(gamesRunning, maxConcurrent)
END AddGame;
PROCEDURE ReportScore(score:LONGINT);
BEGIN {EXCLUSIVE}
highScore := Max(score, highScore)
END ReportScore;
PROCEDURE GetGamesTotal():LONGINT;
BEGIN {EXCLUSIVE}
RETURN gamesTotal
END GetGamesTotal;
PROCEDURE GetMaxConcurrent():LONGINT;
BEGIN {EXCLUSIVE}
RETURN maxConcurrent
END GetMaxConcurrent;
PROCEDURE GetHighscore():LONGINT;
BEGIN {EXCLUSIVE}
RETURN highScore
END GetHighscore;
PROCEDURE GetGamesRunning():LONGINT;
BEGIN {EXCLUSIVE}
RETURN gamesRunning
END GetGamesRunning;
PROCEDURE GetIsShuttingDown():BOOLEAN;
BEGIN {EXCLUSIVE}
RETURN shuttingDown
END GetIsShuttingDown;
PROCEDURE SubGame;
BEGIN {EXCLUSIVE}
DEC(gamesRunning)
END SubGame;
PROCEDURE Max(x, y: LONGINT): LONGINT;
BEGIN
IF x > y THEN RETURN x ELSE RETURN y END
END Max;
PROCEDURE Run*(context : Commands.Context);
BEGIN
ReadData;
server := VNCServer.OpenService(5999, StartTT);
context.out.String("VNC Tetris server started."); context.out.Ln;
END Run;
PROCEDURE StopNew*(context : Commands.Context);
BEGIN {EXCLUSIVE}
shuttingDown := TRUE;
context.out.String("VNC Tetris server shut down."); context.out.Ln;
END StopNew;
PROCEDURE Uninstall*;
VAR f: Files.File; w: Files.Writer;
BEGIN
IF server # NIL THEN
f := Files.New(DataFile);
IF f # NIL THEN
Files.OpenWriter(w, f, 0);
w.RawLInt(highScore);
w.RawLInt(gamesTotal);
w.RawLInt(maxConcurrent);
w.Update();
Files.Register(f)
END;
server.Close
END;
END Uninstall;
PROCEDURE Cleanup;
BEGIN
Uninstall;
END Cleanup;
PROCEDURE ReadData;
VAR f: Files.File; r: Files.Reader;
BEGIN
f := Files.Old(DataFile);
IF f # NIL THEN
Files.OpenReader(r, f, 0);
r.RawLInt(highScore);
r.RawLInt(gamesTotal);
r.RawLInt(maxConcurrent)
END
END ReadData;
BEGIN
Raster.SetRGBA(colors[0], 0, 0, 0, 255);
Raster.SetRGBA(colors[1], 255, 0, 0, 255);
Raster.SetRGBA(colors[2], 0, 255, 0, 255);
Raster.SetRGBA(colors[3], 0, 0, 255, 255);
Raster.SetRGBA(colors[4], 200, 200, 0, 255);
Raster.SetRGBA(colors[5], 255, 0, 255, 255);
Raster.SetRGBA(colors[6], 0, 255, 255, 255);
Raster.SetRGBA(colors[7], 256, 128, 100, 255);
Modules.InstallTermHandler(Cleanup)
END VNCTetrisServer.
Aos.Call VNCTetrisServer.Run
Aos.Call VNCTetrisServer.StopNew
Aos.Call VNCTetrisServer.Uninstall
System.Free VNCTetrisServer VNCServer~