eda2181793ac5e74f901a6b2fa17b1a2b1a3d787
[sdk] / samples / games / blokus / blokus.ec
1 import "ecere"
2 import "console"
3
4 public enum BlokusColor : byte { none, blue, yellow, red, green };
5
6 public enum PlayerColor : byte
7 {
8    blue, yellow, red, green;
9    property BlokusColor
10    {
11       // TOFIX: get { return (BlokusColor)((int)this+1); }
12       get { return (BlokusColor)*(int *)&this+1; }
13    }
14 };
15
16 static Color colors[2][BlokusColor] =
17 {
18    // TOFIX: (omitting Color { })
19    { 0, Color { 64, 64, 96 }, Color { 141, 114, 48 }, Color { 96, 32, 32 }, Color { 64, 96, 64 } },
20    { 0, blue, goldenrod, red, green }
21 };
22 Point corners[PlayerColor] =
23 {
24    { 0, 0 },
25    { bs-1, 0 },
26    { bs-1, bs-1 },
27    { 0, bs-1 }
28 };
29
30 define numPieces = 21;
31 define bs = 20;
32 define squareWidth = 28;
33 define boardStartX = 20;
34 define boardStartY = 30;
35 define piecesY = 20;
36
37 struct Piece
38 {
39    int w, h;
40    byte blocks[25];
41 };
42
43 Piece pieces[numPieces] =
44 {
45    {
46       1, 1,
47       { 1 }
48    },
49    {
50       1, 2,
51       {
52          1,
53          1
54       }
55    },
56    {
57       1,3,
58       {
59          1,
60          1,
61          1
62       }
63    },
64    {
65       2, 2,
66       {
67          1,0,
68          1,1
69       }
70    },
71    {
72       1, 4,
73       {
74          1,
75          1,
76          1,
77          1
78       }
79    }
80    {
81       2,3,
82       {
83          0,1,
84          0,1,
85          1,1
86       }
87    },
88    {
89       2, 3,
90       {
91          1,0,
92          1,1,
93          1,0
94       }
95    },
96    {
97       2,2,
98       {
99          1,1,
100          1,1
101       }
102    },
103    {
104       3,2,
105       {
106          1,1,0,
107          0,1,1
108       }
109    },
110    {
111        1, 5,
112       {
113          1,
114          1,
115          1,
116          1,
117          1
118       }
119    },
120    {
121       2, 4,
122       {
123          0,1,
124          0,1,
125          0,1,
126          1,1
127       }
128    },
129    {
130       2, 4,
131       {
132          0,1,
133          0,1,
134          1,1,
135          1,0
136       }
137    },
138    {
139       2,3,
140       {
141          0,1,
142          1,1,
143          1,1
144       }
145    },
146    {
147       2,3,
148       {
149          1,1,
150          0,1,
151          1,1
152       }
153    },
154    {
155       2,4,
156       {
157          1,0,
158          1,1,
159          1,0,
160          1,0
161       }
162    },
163    {
164       3,3,
165       {
166          0,1,0,
167          0,1,0,
168          1,1,1
169       }
170    },
171    {
172       3,3,
173       {
174          1,0,0,
175          1,0,0,
176          1,1,1
177       }
178    },
179    {
180       3,3,
181       {
182          1,1,0,
183          0,1,1,
184          0,0,1
185       }
186    },
187    {
188       3,3,
189       {
190          1,0,0,
191          1,1,1,
192          0,0,1
193       }
194    },
195    {
196       3,3,
197       {
198          1,0,0,
199          1,1,1,
200          0,1,0
201       }
202    },
203    {
204       3,3,
205       {
206          0,1,0,
207          1,1,1,
208          0,1,0
209       }
210    }
211 };
212
213 struct BlokusGameState
214 {
215    byte playerPieces[PlayerColor][numPieces];
216    bool firstPiece[PlayerColor];
217    BlokusColor board[bs * bs];
218    PlayerColor colorTurn;
219    PlayerColor rotatingColor;
220    int numPlayers;
221    bool validPieces[numPieces];
222    bool over;
223    bool validMove;
224    int bonus[4];
225    int scores[4];
226
227    void NewGame()
228    {
229       PlayerColor p;
230       int i;
231
232       // TOFIX: for(p = 0; p < PlayerColor::enumSize; p++)
233       for(p = blue; p <= green; p++)
234       {
235          for(i = 0; i < numPieces; i++)
236             playerPieces[p][i] = 1;
237          firstPiece[p] = true;
238          bonus[p] = 0;
239          scores[0] = 0;
240       }
241       for(i = 0; i < bs * bs; i++)
242          board[i] = none;
243
244       over = false;
245       colorTurn = blue;
246       rotatingColor = blue;
247
248       validMove = CheckValidMoves(colorTurn, validPieces);
249    }
250
251    bool ValidMove(PlayerColor playerColor, int selectedPiece, int direction, bool flip, int boardX, int boardY)
252    {
253       Piece * piece = &pieces[selectedPiece];
254       int x, y;
255       bool valid = true;
256       bool touchCorner = false;
257       int w = (direction & 1) ? piece->h : piece->w;
258       int h = (direction & 1) ? piece->w : piece->h;
259
260       if(!playerPieces[playerColor][selectedPiece]) return false;
261
262       if(boardX < 0 || boardY < 0 || boardX + w > bs || boardY + h > bs) return false;
263
264       for(y = 0; y < 5; y++)
265          for(x = 0; x < 5; x++)
266             if(PieceBlock(selectedPiece, x, y, direction, flip))
267             {
268                int bx = x + boardX, by = y + boardY;
269                if(board[by * bs + bx] ||
270                   (by > 0    && board[(by-1) * bs + bx] == playerColor) ||
271                   (by < bs-1 && board[(by+1) * bs + bx] == playerColor) ||
272                   (bx > 0    && board[by * bs + bx - 1] == playerColor) ||
273                   (bx < bs-1 && board[by * bs + bx + 1] == playerColor))
274                {
275                   valid = false;
276                   break;
277                }
278                if((by > 0     && bx > 0    && board[(by-1) * bs + (bx-1)] == playerColor) ||
279                   (by > 0     && bx < bs-1 && board[(by-1) * bs + (bx+1)] == playerColor) ||
280                   (by < bs-1  && bx > 0    && board[(by+1) * bs + (bx-1)] == playerColor) ||
281                   (by < bs-1  && bx < bs-1 && board[(by+1) * bs + (bx+1)] == playerColor))
282                   touchCorner = true;
283             }
284       if(valid && firstPiece[playerColor])
285       {
286          for(y = 0; y < 5; y++)
287             for(x = 0; x < 5; x++)
288                if(PieceBlock(selectedPiece, x, y, direction, flip))
289                {
290                   int bx = x + boardX, by = y + boardY;
291                   if(bx == corners[playerColor].x && by == corners[playerColor].y)
292                   {
293                      touchCorner = true;
294                      break;
295                   }
296                }
297       }
298
299       return valid && touchCorner;
300    }
301
302    bool CheckValidMoves(PlayerColor playerColor, bool validPieces[21])
303    {
304       bool result = false;
305       int p;
306       for(p = 0; p < numPieces; p++)
307       {
308          bool validMove = false;
309          if(playerPieces[playerColor][p])
310          {
311             int x, y;
312             for(y = 0; y < bs && !validMove; y++)
313             {
314                for(x = 0; x < bs && !validMove; x++)
315                {
316                   int flip;
317                   int direction;
318                   for(direction = 0; direction < 4 && !validMove; direction++)
319                   {
320                      for(flip = 0; flip <=1 && !validMove; flip++)
321                      {
322                         if(ValidMove(playerColor, p, direction, flip, x, y))
323                            result = validMove = true;
324                      }
325                   }
326                }
327             }
328          }
329          if(validPieces) validPieces[p] = validMove;
330       }
331       return result;
332    }
333
334    void PlayMove(int pieceType, int direction, bool flip, int boardX, int boardY)
335    {
336       int p;
337       int y, x;
338       for(y = 0; y < 5; y++)
339          for(x = 0; x < 5; x++)
340             if(PieceBlock(pieceType, x, y, direction, flip))
341                board[(y + boardY) * bs + x + boardX] = colorTurn;
342       playerPieces[colorTurn][pieceType] = 0;
343       scores[colorTurn] = 0;
344       for(p = 0; p < numPieces; p++)
345       {
346          if(playerPieces[colorTurn][p])
347          {
348             Piece * piece = &pieces[p];
349             int y, x;
350             for(y = 0; y < piece->h; y++)
351                for(x = 0; x < piece->w; x++)
352                   if(piece->blocks[y * piece->w + x])
353                      scores[colorTurn]--;
354          }
355       }
356       if(scores[colorTurn] == 0)
357          bonus[colorTurn] = (pieceType == 0) ? 20 : 15;
358
359       firstPiece[colorTurn] = false;
360       if(numPlayers == 3 && colorTurn == green)
361       {
362          if(++rotatingColor == green) rotatingColor = 0;
363       }
364       if(++colorTurn > green) colorTurn = 0;
365
366       validMove = CheckValidMoves(colorTurn, validPieces);
367       if(!validMove)
368          CheckGameOver();
369    }
370
371    void Pass()
372    {
373       if(numPlayers == 3 && colorTurn == green)
374       {
375          if(++rotatingColor == green) rotatingColor = 0;
376       }
377       if(++colorTurn > green) colorTurn = 0;
378
379       validMove = CheckValidMoves(colorTurn, validPieces);
380       if(!validMove)
381          CheckGameOver();
382    }
383
384    void CheckGameOver()
385    {
386       PlayerColor turn = colorTurn;
387       int c;
388       for(c = 0; c < 3; c++)
389       {
390          if(++turn > green) turn = 0;
391          if(CheckValidMoves(turn, null))
392             break;
393       }
394       if(c == 3)
395          over = true;
396    }
397 };
398
399 int PieceBlock(int p, int x, int y, int direction, bool flip)
400 {
401    Piece * piece = &pieces[p];
402    int w = piece->w, h = piece->h;
403    int rx, ry;
404    switch(direction)
405    {
406       case 0: rx = x; ry = y; break;
407       case 1: ry = h-1 - x; rx = y; break;
408       case 2: ry = h-1 - y; rx = w -1- x; break;
409       case 3: ry = x; rx = w-1-y; break;
410    }
411    if(rx < w && ry < h && rx  >= 0 && ry >= 0)
412       return piece->blocks[ry * w + (flip ? (w-1-rx) : rx)];
413    return 0;
414 }
415
416 class Blokus : Window
417 {
418    text = "Blokus";
419    background = black;
420    borderStyle = sizable;
421    minClientSize = { 1068 /*800*/, 700 };
422    hasMaximize = true;
423    hasMinimize = true;
424    hasClose = true;
425    size = { 1280, 728 };
426    nativeDecorations = true;
427    font = { "Arial", 12, bold = true };
428    FontResource yourTurnFont { "Arial", 12, bold = true, italic = true, window = this };
429
430    bool gameStarted;
431    PlayerColor firstColor;
432    char *playerNames[MaxPlayers];
433
434    // Current game state
435    BlokusGameState gameState;
436
437    // User Interaction
438    PlayerColor colorPlayed; // Color currently (or next) being played
439    int selectedPiece;
440    bool dragging, onBoard;
441    Point drag, offset;
442    Point squareDragged;
443    Point boardPos;
444    int direction;
445    bool flip;
446    bool passed[4];
447
448    void NextColorPlayed()
449    {
450       if(gameState.numPlayers == 1)
451       {
452          if(++colorPlayed > green) colorPlayed = 0;
453       }
454       else if(gameState.numPlayers == 2)
455       {
456          if((colorPlayed+=2) > green) colorPlayed = firstColor;
457       }
458       else if(gameState.numPlayers == 3)
459       {
460          colorPlayed = (colorPlayed == firstColor && gameState.rotatingColor == firstColor) ? green : firstColor;
461       }
462    }
463
464    void UpdatePlayerNames()
465    {
466       int c;
467       if(gameState.numPlayers == 1)
468       {
469          for(c = 0; c < 4; c++)
470             playerNames[c] = panel.playerNames[0];
471       }
472       else if(gameState.numPlayers == 2)
473       {
474          for(c = 0; c < 2; c++)
475          {
476             playerNames[c] = panel.playerNames[c];
477             playerNames[c+2] = panel.playerNames[c];
478          }
479       }
480       else if(gameState.numPlayers == 3)
481       {
482          for(c = 0; c < 3; c++)
483             playerNames[c] = panel.playerNames[c];
484          playerNames[3] = panel.playerNames[gameState.rotatingColor];
485       }
486       else if(gameState.numPlayers == 4)
487       {
488          for(c = 0; c < 4; c++)
489             playerNames[c] = panel.playerNames[c];
490       }
491    }
492
493    void DrawSquare(Surface surface, int x, int y, BlokusColor color, int shade)
494    {
495       surface.background = colors[shade][color];
496       surface.Area(x+1, y+1, x + squareWidth-1, y + squareWidth-1);      
497       surface.foreground = lightGray;
498       surface.VLine(y+5, y + 15, x + 5);
499       surface.HLine(x+5, x + 18, y + 5);
500       surface.foreground = white;
501       surface.Rectangle(x + 2,y+2, x + squareWidth-2, y + squareWidth - 2);
502    }
503
504    void OnDestroy()
505    {
506       panel.Destroy(0);
507       scoresPanel.Destroy(0);
508    }
509
510    bool OnMouseMove(int mx, int my, Modifiers mods)
511    {
512       if(dragging)
513       {
514          Piece * piece = &pieces[selectedPiece];
515          int w = (direction & 1) ? piece->h : piece->w;
516          int h = (direction & 1) ? piece->w : piece->h;
517          drag = { offset.x + mx, offset.y + my }; 
518
519          if(mx - squareDragged.x * squareWidth >= boardStartX - 10 && mx - squareDragged.x * squareWidth < boardStartX + ((bs-w)+1) * squareWidth + 10 && 
520             my - squareDragged.y * squareWidth >= boardStartY - 10 && my - squareDragged.y * squareWidth < boardStartY + ((bs-h)+1) * squareWidth + 10)
521          {
522             int x, y;
523             x = Max(0,mx - squareDragged.x * squareWidth - boardStartX) / squareWidth;
524             y = Max(0,my - squareDragged.y * squareWidth - boardStartY) / squareWidth;
525             x = Min(x, bs-w);
526             y = Min(y, bs-h);
527             drag.x = boardStartX + x * squareWidth;
528             drag.y = boardStartY + y * squareWidth;
529             boardPos = { x, y };
530             onBoard = true;
531          }
532          else
533             onBoard = false;
534          Update(null);
535       }
536       return true;
537    }
538
539    bool OnLeftButtonUp(int mx, int my, Modifiers mods)
540    {
541       if(dragging)
542       {
543          if(onBoard)
544          {
545             if(gameStarted && colorPlayed == gameState.colorTurn)
546             {
547                Piece * piece = &pieces[selectedPiece];
548                if(gameState.ValidMove(gameState.colorTurn, selectedPiece, direction, flip, boardPos.x, boardPos.y))
549                   panel.server.PlayPiece(selectedPiece, direction, flip, boardPos.x, boardPos.y);
550             }
551          }
552          dragging = false;
553          ReleaseCapture();
554          Update(null);
555       }
556       return true;
557    }
558
559    bool OnRightButtonDown(int mx, int my, Modifiers mods)
560    {
561       Piece * piece = &pieces[selectedPiece];
562       int x = squareDragged.x, y = squareDragged.y;
563       int w,h;
564
565       w = (direction & 1) ? piece->h : piece->w;
566       h = (direction & 1) ? piece->w : piece->h;
567
568       offset.x += squareDragged.x * squareWidth;
569       offset.y += squareDragged.y * squareWidth;
570       squareDragged = (direction & 1) ? { x, h-1-y } : { w-1-x, y };
571       flip ^= true;
572       offset.x -= squareDragged.x * squareWidth;
573       offset.y -= squareDragged.y * squareWidth;
574
575       OnMouseMove(mx,my,mods);
576
577       Update(null);
578       return true;
579    }
580
581    bool OnKeyHit(Key key, unichar ch)
582    {
583       if(key == wheelDown || key == wheelUp)
584       {
585          Piece * piece = &pieces[selectedPiece];
586          int mx = drag.x - offset.x, my = drag.y - offset.y;
587
588          int rx, ry;
589          int x = squareDragged.x, y = squareDragged.y;
590          int w,h;
591
592          if(key == wheelDown)
593          {
594             if(++direction == 4) direction = 0;
595          }
596          else
597          {
598             if(--direction == -1) direction = 3;
599          }
600          w = (direction & 1) ? piece->h : piece->w;
601          h = (direction & 1) ? piece->w : piece->h;
602
603          offset.x += squareDragged.x * squareWidth;
604          offset.y += squareDragged.y * squareWidth;
605          if(key == wheelDown)
606             squareDragged = { w-1-y, x };
607          else
608             squareDragged = { y, h-1-x };
609             
610          offset.x -= squareDragged.x * squareWidth;
611          offset.y -= squareDragged.y * squareWidth;
612
613          OnMouseMove(mx,my,0);
614
615          Update(null);
616       }
617       return true;
618    }
619
620    bool OnLeftButtonDown(int mx, int my, Modifiers mods)
621    {
622       if( mx > squareWidth * bs + 40)
623       {
624          int bx, by;
625          bool selected = false;
626          int pieceNum = 0;
627          Point offset;
628          int rh = 0;
629          int c;
630          // Draw Player Pieces
631          bx = squareWidth * bs + 40;
632          by = piecesY;
633          for(c = 0; c < numPieces; c++)
634          {
635             if(gameState.playerPieces[colorPlayed][c])
636             {
637                int w = pieces[c].w, h = pieces[c].h;
638                int y;
639                for(y = 0; y < 5; y++)
640                {
641                   int x;
642                   for(x = 0; x < 5; x++)
643                   {
644                      if(PieceBlock(c, x, y, 0, false))
645                      {
646                         int dx = bx + x * squareWidth;
647                         int dy = by + y * squareWidth;
648                         if(mx >= dx && mx < dx + squareWidth &&
649                            my >= dy && my < dy + squareWidth)
650                         {
651                            selected = true;
652                            pieceNum = c;
653                            offset = { bx - mx, by - my };
654                            squareDragged = { x, y };
655                            break;
656                         }
657                      }
658                      if(selected) break;
659                   }
660                   if(selected) break;
661                }
662                if(selected) break;
663                bx += w * squareWidth + squareWidth;
664                if(h > rh) rh = h;
665                if(bx + 5 * squareWidth > clientSize.w)
666                {
667                   bx = squareWidth * bs + 40;
668                   by += (rh+1) * squareWidth;
669                   rh = 0;
670                }
671             }
672          }
673          if(selected)
674          {
675             Update(null);
676             direction = 0;
677             flip = 0;
678             selectedPiece = pieceNum;
679             this.offset = offset;
680             dragging = true;
681             OnMouseMove(mx, my, mods);
682             Capture();
683          }
684       }
685       return true;
686    }
687
688    float lightValue, lightDir;
689    lightValue = 1;
690    lightDir = -.1f;
691    Timer turnLightTimer
692    {
693       this, 0.1, false;
694
695       bool DelayExpired()
696       {
697          lightValue += lightDir;
698          if(lightValue < 0) { lightValue = 0; lightDir = .1f; }
699          if(lightValue > 1) { lightValue = 1; lightDir =-.1f; }
700          Update(null);
701          return true;
702       }
703    };
704
705    void OnRedraw(Surface surface)
706    {
707       int c;
708       int bx = boardStartX;
709       int by = boardStartY;
710       int x, y;
711       char * s;
712       int len, tw;
713       Color turnLight = white;
714       if(gameStarted && !gameState.over)
715       {
716          ColorRGB empty = colors[1][gameState.colorTurn] /*gray*/, full = white;
717          turnLight = ColorRGB
718          {
719             empty.r + lightValue * (full.r - empty.r),
720             empty.g + lightValue * (full.g - empty.g),
721             empty.b + lightValue * (full.b - empty.b)
722          };
723       }
724       
725       surface.foreground = aqua;
726       for(c = 0; c <= bs; c++)
727       {
728          surface.HLine(bx,bx+squareWidth*bs, by + c * squareWidth);
729          surface.VLine(by,by+squareWidth*bs, bx + c * squareWidth);
730       }
731
732       surface.background = colors[blokus.gameStarted][blue];
733       x = bx;
734       y = by;
735       surface.Area(x - 10, y - 10, x + 10, y-1);
736       surface.Area(x - 10, y - 10, x - 1, y+10);
737
738       s = playerNames[PlayerColor::blue];
739       if(s)
740       {
741          len = strlen(s);
742          surface.foreground = (gameState.colorTurn == blue) ? turnLight : white;
743          surface.WriteText(x + 15, y - 20, s, len);
744       }
745
746       surface.background = colors[blokus.gameStarted][yellow];
747       x = bx + bs*squareWidth;
748       y = by;
749       surface.Area(x + 10, y - 10, x - 10, y-1);
750       surface.Area(x + 10, y - 10, x + 1,  y+10);
751
752       s = playerNames[PlayerColor::yellow];
753       if(s)
754       {
755          len = strlen(s);
756          surface.TextExtent(s, len, &tw, null);
757          surface.foreground = (gameState.colorTurn == yellow) ? turnLight : white;
758          surface.WriteText(x - 15 - tw, y - 20, s, len);
759       }
760
761       surface.background = colors[blokus.gameStarted][red];
762       x = bx + bs*squareWidth;
763       y = by + bs*squareWidth;
764       surface.Area(x + 10, y + 1, x - 10, y+10);
765       surface.Area(x + 10, y - 10, x + 1,  y+10);
766       s = playerNames[PlayerColor::red];
767       if(s)
768       {
769          len = strlen(s);
770          surface.TextExtent(s, len, &tw, null);
771          surface.foreground = (gameState.colorTurn == red) ? turnLight : white;
772          surface.WriteText(x - 15 - tw, y, s, len);
773       }
774
775       surface.background = colors[blokus.gameStarted][green];
776       x = bx;
777       y = by + bs*squareWidth;
778       surface.Area(x - 10, y + 1, x + 10, y+10);
779       surface.Area(x - 10, y - 10, x - 1,  y+10);
780       s = playerNames[PlayerColor::green];
781       if(s)
782       {
783          len = strlen(s);
784          surface.foreground = (gameState.colorTurn == green) ? turnLight : white;
785          surface.WriteText(x + 15, y, s, len);
786       }
787
788       if(gameState.over)
789       {
790          surface.font = yourTurnFont.font;
791          surface.foreground = crimson;
792          surface.CenterTextf(x + bs*squareWidth/2, y + 3, "Game Over");
793       }
794       else if(gameState.numPlayers > 1 && gameStarted && colorPlayed == gameState.colorTurn)
795       {
796          surface.font = yourTurnFont.font;
797          surface.foreground = tomato;
798          surface.CenterTextf(x + bs*squareWidth/2, y + 3, "Your turn");
799       }
800
801       for(y = 0; y < bs; y++)
802       {
803          for(x = 0; x < bs; x++)
804          {
805             BlokusColor color = gameState.board[y * bs + x];
806             if(color)
807             {
808                DrawSquare(surface, bx + x * squareWidth, by + y * squareWidth, color, blokus.gameStarted);
809             }
810          }
811       }
812
813       {
814          int rh = 0;
815          // Draw Player Pieces
816          bx = squareWidth * bs + 40;
817          by = piecesY;
818          for(c = 0; c < numPieces; c++)
819          {
820             if(gameState.playerPieces[colorPlayed][c])
821             {
822                int w = pieces[c].w, h = pieces[c].h;
823                for(y = 0; y < 5; y++)
824                {
825                   for(x = 0; x < 5; x++)
826                   {
827                      if(PieceBlock(c, x, y, 0, false))
828                      {
829                         if(!dragging || selectedPiece != c)
830                            DrawSquare(surface, bx + x * squareWidth, by + y * squareWidth, colorPlayed, gameStarted && (gameState.colorTurn != colorPlayed || gameState.validPieces[c]));
831                      }
832                   }
833                }
834                if(h > rh) rh = h;
835                bx += w * squareWidth + squareWidth;
836                if(bx + 5 * squareWidth > clientSize.w)
837                {
838                   bx = squareWidth * bs + 40;
839                   by += rh * squareWidth + squareWidth;
840                   rh = 0;
841                }
842             }
843          }
844          // Draw Dragged piece
845          if(dragging)
846          {
847             for(y = 0; y < 5; y++)
848                for(x = 0; x < 5; x++)
849                {
850                   if(PieceBlock(selectedPiece, x, y, direction, flip))
851                   {
852                      DrawSquare(surface, drag.x + x * squareWidth, drag.y + y * squareWidth, colorPlayed, gameStarted && gameState.colorTurn == colorPlayed);
853                      
854                      if(x == 0 || !PieceBlock(selectedPiece, x-1, y, direction, flip))
855                      {
856                         surface.foreground = white;
857                         surface.VLine(drag.y + y * squareWidth-1, drag.y + (y+1) * squareWidth+1, drag.x + x * squareWidth - 1);
858                         surface.foreground = lime;
859                         surface.VLine(drag.y + y * squareWidth-2, drag.y + (y+1) * squareWidth+2, drag.x + x * squareWidth - 2);
860                      }
861
862                      if(y == 0 || !PieceBlock(selectedPiece, x, y-1, direction, flip))
863                      {
864                         surface.foreground = white;
865                         surface.HLine(drag.x + x * squareWidth-1, drag.x + (x+1) * squareWidth+1, drag.y + y * squareWidth - 1);
866                         surface.foreground = lime;
867                         surface.HLine(drag.x + x * squareWidth-2, drag.x + (x+1) * squareWidth+2, drag.y + y * squareWidth - 2);
868                      }
869
870                      if(x == 4 || !PieceBlock(selectedPiece, x+1, y, direction, flip))
871                      {
872                         surface.foreground = white;
873                         surface.VLine(drag.y + y * squareWidth-1, drag.y + (y+1) * squareWidth+1, drag.x + (x+1) * squareWidth + 1);
874                         surface.foreground = lime;
875                         surface.VLine(drag.y + y * squareWidth-2, drag.y + (y+1) * squareWidth+2, drag.x + (x+1) * squareWidth + 2);
876                      }
877
878                      if(y == 4 || !PieceBlock(selectedPiece, x, y+1, direction, flip))
879                      {
880                         surface.foreground = white;
881                         surface.HLine(drag.x + x * squareWidth-1, drag.x + (x+1) * squareWidth+1, drag.y + (y+1) * squareWidth + 1);
882                         surface.foreground = lime;
883                         surface.HLine(drag.x + x * squareWidth-2, drag.x + (x+1) * squareWidth+2, drag.y + (y+1) * squareWidth + 2);
884                      }
885                   }
886                }
887          }
888       }
889    }
890
891    Console chat
892    {
893       this, size = { bs*squareWidth }, anchor = { left = boardStartX, top = boardStartY + bs*squareWidth + 20, bottom = 5 };
894       font = { "Arial", 11, bold = true };
895       editTextColor = white;
896       logTextColor = white;
897       editHeight = 24;
898       log.hasVertScroll = bool::true;
899       visible = false;
900
901       bool ProcessCommand(char * command)
902       {
903          panel.server.SendMessage(command);
904          return false;
905       }
906    };
907
908    Button btnPass
909    {
910       this, text = "No Move Available! Pass...", anchor = { right = 5, bottom = 5 };
911       inactive = true;
912       visible = false;
913
914       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
915       {
916          btnPass.visible = false;
917          blokus.passed[blokus.gameState.colorTurn] = true;
918          panel.server.Pass();
919          return true;
920       }
921
922    };
923 }
924
925 class BlokusScores : Window
926 {
927    master = blokus;
928    moveable = true;
929    borderStyle = fixed;
930    background = black;
931    text = "Blokus Final Scores";
932    clientSize = { 580, 210 };
933    font = { "Arial", 12, bold = true };
934
935    void OnRedraw(Surface surface)
936    {
937       PlayerColor p;
938       char * s;
939       int len;
940       char temp[256];
941       int grandTotals[4];
942       BlokusGameState * state = &blokus.gameState;
943       surface.foreground = white;
944       s = "Score"; len = strlen(s);
945       surface.WriteText(10, 40, s, len);
946
947       s = "Bonus"; len = strlen(s);
948       surface.WriteText(10, 60, s, len);
949
950       s = "Total"; len = strlen(s);
951       surface.WriteText(10, 100, s, len);
952
953       if(state->numPlayers < 3)
954       {
955          s = "Grand Total"; len = strlen(s);
956          surface.WriteText(10, 160, s, len);
957       }
958
959       for(p = blue; p <= green; p++)
960       {
961          // TOFIX: bug here, why is -1 required?
962          int x = 80 + (p-1) * 120;
963          surface.foreground = colors[1][p];
964          /* // GCC internal compiler error with -O2, MinGW GCC 4.4.0
965          s = (state->numPlayers == 3 && p == green) ? "* Green *" : blokus.playerNames[p];
966          len = strlen(s);
967          surface.WriteText(x, 20, s, len);
968          */
969          if(state->numPlayers == 3 && p == green)
970          {
971             s = "* Green *";
972             len = strlen(s);
973             surface.WriteText(x, 20, s, len);
974          }
975          else if(blokus.playerNames[p])
976          {
977             s = blokus.playerNames[p];
978             len = strlen(s);
979             surface.WriteText(x, 20, s, len);
980          }
981
982          s = temp; sprintf(temp, "%d", state->scores[p]);
983          len = strlen(s);
984          surface.WriteText(x + 30, 40, s, len);
985
986          if(state->bonus[p])
987          {
988             s = temp; sprintf(temp, "%d", state->bonus[p]);
989             len = strlen(s);
990             surface.WriteText(x + 30, 60, s, len);
991          }
992
993          if(state->numPlayers > 2)
994             grandTotals[p] = state->scores[p] + state->bonus[p];
995          s = temp; sprintf(temp, "%d", state->scores[p] + state->bonus[p]);
996          len = strlen(s);
997          surface.WriteText(x + 30, 100, s, len);
998
999          if((state->numPlayers == 2 && p <= yellow) ||
1000             (state->numPlayers == 1 && p == blue))
1001          {
1002             // TOFIX: Annoying +2 conversion issue
1003             if(state->numPlayers == 2)
1004                grandTotals[p] = state->scores[p] + state->bonus[p] +
1005                                 state->scores[p+red /*2*/] + state->bonus[p+red /*2*/];
1006             else
1007                grandTotals[p] = state->scores[0] + state->bonus[0] +
1008                                 state->scores[1] + state->bonus[1] +
1009                                 state->scores[2] + state->bonus[2] +
1010                                 state->scores[3] + state->bonus[3];
1011
1012             if(blokus.playerNames[p])
1013             {
1014                s = blokus.playerNames[p];
1015                len = strlen(s);
1016                surface.WriteText(x, 140, s, len);
1017             }
1018
1019             s = temp; sprintf(temp, "%d", grandTotals[p]);
1020             len = strlen(s);
1021             surface.WriteText(x + 30, 160, s, len);
1022          }
1023       }
1024       if(state->numPlayers > 1)
1025       {
1026          char string[256];
1027          int c, greatest = -MAXINT;
1028          int numTies = 0, ties[3], winner;
1029
1030          string[0] = 0;
1031          for(c = 0; c < state->numPlayers; c++)
1032          {
1033             if(grandTotals[c] > greatest)
1034             {
1035                greatest = grandTotals[c];
1036                numTies = 0;
1037                winner = c;
1038             }
1039             else if(grandTotals[c] == greatest)
1040             {
1041                if(!numTies)
1042                {
1043                   numTies = 1;
1044                   ties[0] = winner;
1045                }
1046                ties[numTies++] = c;
1047             }
1048          }
1049          if(numTies)
1050          {
1051             for(c = 0; c < numTies; c++)
1052             {
1053                strcat(string, blokus.playerNames[c]);
1054                if(c < numTies-2)
1055                   strcat(string, ", ");
1056                else if(c < numTies-1)
1057                   strcat(string, " and ");
1058             }
1059             surface.foreground = white;
1060             strcat(string, " tied!");
1061          }
1062          else
1063          {
1064             surface.foreground = colors[1][(PlayerColor)winner];
1065             sprintf(string, "%s won!", blokus.playerNames[winner]);
1066          }
1067
1068          len = strlen(string);
1069          surface.WriteText(100, 180, string, strlen(string));
1070       }
1071    }
1072 }
1073
1074 BlokusScores scoresPanel { visible = false };
1075
1076 Blokus blokus { };
1077
1078 class BlokusApp : GuiApplication
1079 {
1080    bool Init()
1081    {
1082       blokus.Create();
1083       panel.Create();
1084       return true;
1085    }
1086
1087    bool Cycle(bool idle)
1088    {
1089       // This is here because it hangs in MovePlayed() (Why?)
1090       scoresPanel.visible = blokus.gameStarted && blokus.gameState.over;
1091       return true;
1092    }
1093    void Terminate()
1094    {
1095       if(hosting)
1096          blokusService.Stop();
1097    }
1098 }
1099
1100 import remote "BlokusServer"
1101
1102 define app = ((BlokusApp)__thisModule.application);
1103
1104 define BLOKUS_PORT = 1495;
1105 static bool hosting;
1106 define MaxPlayers = 4;
1107
1108 class CommunicationPanel : Window
1109 {
1110    text = "Blokus Communication Panel";
1111    background = lightSlateGray;
1112    borderStyle = fixed;
1113    hasClose = true;
1114    tabCycle = true;
1115    size = { 400, 300 };
1116    anchor = { horz = -3, vert = -7 };
1117    nativeDecorations = true;
1118
1119    ServerConnection server;
1120
1121    // Other player info
1122    char playerNames[MaxPlayers][256];
1123
1124    DataField fldName { header = "Name", width = 100 };
1125    DataField fldAddr { header = "Address" };
1126
1127    void OnDestroy()
1128    {
1129       app.Unlock();
1130       if(hosting)
1131          blokusService.Stop();
1132       app.Lock();
1133
1134       if(panel.server)
1135          panel.server.Disconnect(0);
1136       blokus.Destroy(0);
1137    }
1138
1139    CommunicationPanel()
1140    {
1141       listPlayers.AddField(fldName);
1142       listPlayers.AddField(fldAddr);
1143    }
1144
1145    ~CommunicationPanel()
1146    {
1147       delete server;
1148    }
1149
1150    void ListPlayers()
1151    {
1152       int c;
1153       listPlayers.Clear();
1154       fldAddr.header = hosting ? "Address" : "";
1155       for(c = 0; c<MaxPlayers; c++)
1156       {
1157          if(hosting)
1158          {
1159             if(serverPlayers[c])
1160             {
1161                DataRow row = listPlayers.AddRow();
1162                DCOMServerObject object = (DCOMServerObject)serverPlayers[c].connection._vTbl[-1];
1163
1164                row.tag = serverPlayers[c].id;
1165                row.SetData(fldName, serverPlayers[c].name);
1166                row.SetData(fldAddr, object.serverSocket.inetAddress);
1167             }
1168          }
1169          else if(playerNames[c][0])
1170          {
1171             DataRow row = listPlayers.AddRow();
1172             row.SetData(fldName, playerNames[c]);
1173          }
1174       }
1175    }
1176
1177    void UpdateControlsStates()
1178    {
1179       int numPlayers = 0;
1180       if(hosting && !serverGameStarted)
1181       {
1182          int c;
1183          for(c = 0; c<MaxPlayers; c++)
1184             if(serverPlayers[c])
1185                numPlayers++;
1186       }
1187       lblServerAddress.disabled = serverAddress.disabled = server ? true : false;
1188       lblPlayerName.disabled = playerName.disabled = server ? true : false;
1189       lblServerAddress.Update(null);
1190       lblPlayerName.Update(null);
1191       btnConnect.visible = server ? false : true;
1192       btnDisconnect.visible = server ? true : false;
1193       blokus.chat.visible = server ? true : false;
1194
1195       btnHost.visible = !hosting && !server;
1196       btnStopHosting.visible = hosting;
1197       btnStart.visible = hosting && !serverGameStarted && numPlayers > 0;
1198       btnStopGame.visible = hosting && serverGameStarted;
1199       listPlayers.visible = (hosting && (serverGameStarted || numPlayers > 0)) || (!hosting && server && blokus.gameStarted);
1200       btnKick.visible = hosting && !serverGameStarted && numPlayers > 0;
1201       btnKick.disabled = listPlayers.currentRow ? false : true;
1202    }
1203    EditBox serverAddress
1204    {
1205       this, text = "Server Address:", altA, font = { "Tahoma", 10, bold = true }, size = { 220, 24 }, position = { 16, 64 }, contents = "localhost"
1206    };
1207    Label lblServerAddress { this, font = { "Tahoma", 8.25f, bold = true }, position = { 16, 40 }, labeledWindow = serverAddress };
1208    Button btnConnect
1209    {
1210       this, text = "Connect", altC, isDefault = true, font = { "Arial", 16, bold = true }, size = { 126, 32 }, position = { 256, 64 };
1211
1212       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1213       {
1214          if(!server)
1215          {
1216             server = ServerConnection
1217             {
1218                void OnDisconnect(int code)
1219                {
1220                   app.Lock();
1221                   if(blokus)
1222                   {
1223                      delete panel.server;
1224                      blokus.gameStarted = false;
1225                      blokus.turnLightTimer.Stop();
1226                      blokus.lightValue = 1;
1227                      blokus.lightDir = -.1f;
1228                      blokus.Update(null);
1229                      panel.UpdateControlsStates();
1230                      panel.ListPlayers();
1231                   }
1232                   DCOMClientObject::OnDisconnect(code);
1233                   app.Unlock();
1234                }
1235
1236                void GameStarted(GameInfo gameInfo)
1237                {
1238                   int x,y;
1239                   int c, np = 0;
1240
1241                   blokus.gameState.numPlayers = gameInfo.numPlayers;
1242                   blokus.firstColor = gameInfo.firstColor;
1243                   blokus.colorPlayed = blokus.firstColor;
1244
1245                   for(c = 0; c<MaxPlayers; c++)
1246                   {
1247                      if(gameInfo.players[c][0])
1248                         strcpy(panel.playerNames[np++], gameInfo.players[c]);
1249                   }
1250
1251                   blokus.btnPass.visible = false;
1252                   blokus.gameState.NewGame();
1253                   blokus.passed[0] = false;
1254                   blokus.passed[1] = false;
1255                   blokus.passed[2] = false;
1256                   blokus.passed[3] = false;
1257                   blokus.gameStarted = true;
1258                   blokus.lightValue = 1;
1259                   blokus.lightDir = -.1f;
1260                   blokus.turnLightTimer.Start();
1261                   blokus.UpdatePlayerNames();
1262
1263                   blokus.Update(null);
1264
1265                   panel.UpdateControlsStates();
1266                   panel.ListPlayers();
1267                }
1268
1269                void GameEnded()
1270                {
1271                   int c;
1272
1273                   panel.ListPlayers();
1274
1275                   blokus.gameStarted = false;
1276                   blokus.Update(null);
1277                }
1278
1279                void MovePlayed(PlayerColor color, int pieceType, int direction, bool flip, int boardX, int boardY)
1280                {
1281                   blokus.gameState.PlayMove(pieceType, direction, flip, boardX, boardY);
1282                   if(color == blokus.colorPlayed)
1283                      blokus.NextColorPlayed();
1284
1285                   if(blokus.colorPlayed == blokus.gameState.colorTurn &&
1286                      !blokus.gameState.validMove && !blokus.gameState.over)
1287                   {
1288                      if(!blokus.passed[blokus.gameState.colorTurn])
1289                         blokus.btnPass.visible = true;
1290                      else
1291                         panel.server.Pass();
1292                   }
1293
1294                   // This hangs here, why?
1295                   /*if(blokus.gameState.over)
1296                      scoresPanel.visible = true;*/
1297                   if(blokus)
1298                   {
1299                      blokus.UpdatePlayerNames();
1300                      blokus.Update(null);
1301                   }
1302                }
1303
1304                void Passed(PlayerColor color)
1305                {
1306                   blokus.gameState.Pass();
1307                   if(color == blokus.colorPlayed)
1308                      blokus.NextColorPlayed();
1309
1310                   if(blokus.colorPlayed == blokus.gameState.colorTurn &&
1311                      !blokus.gameState.validMove && !blokus.gameState.over)
1312                   {
1313                      if(!blokus.passed[blokus.gameState.colorTurn])
1314                         blokus.btnPass.visible = true;
1315                      else
1316                         panel.server.Pass();
1317                   }
1318
1319                   if(blokus)
1320                   {
1321                      blokus.UpdatePlayerNames();
1322                      blokus.Update(null);
1323                   }
1324                }
1325
1326                void NotifyMessage(String name, String msg)
1327                {
1328                   EditBox log = blokus.chat.log;
1329                   char * format = (log.numLines > 1 || log.line.count) ?
1330                      "\n%s: %s" : "%s: %s";
1331                   blokus.chat.Log(format, name, msg);
1332                }
1333             };
1334             incref panel.server;
1335             if(panel.server.Connect(serverAddress.contents, BLOKUS_PORT))
1336             {
1337                int playerID = panel.server.Join();
1338                if(panel.server && playerID != -1)
1339                   panel.server.SetName(playerName.contents);
1340                else
1341                   panel.server.Disconnect(0);
1342                UpdateControlsStates();
1343             }
1344             else
1345                delete panel.server;
1346          }
1347          return true;
1348       }
1349    };
1350    Button btnDisconnect
1351    {
1352       this, text = "Disconnect", altD, font = { "Arial", 16, bold = true }, size = { 126, 32 }, position = { 256, 64 }, visible = false;
1353
1354       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1355       {
1356          if(panel.server)
1357             panel.server.Disconnect(0);
1358          return true;
1359       }
1360    };
1361    Button btnHost
1362    {
1363       this, text = "Host", altH, font = { "Arial", 16, bold = true }, size = { 90, 32 }, position = { 16, 112 };
1364
1365       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1366       {
1367          if(!blokus.gameStarted)
1368          {
1369             if(blokusService.Start())
1370             {
1371                hosting = true;
1372                Update(null);
1373                UpdateControlsStates();
1374             }
1375          }
1376          return true;
1377       }
1378    };
1379    Button btnStopHosting
1380    {
1381       this, text = "Stop Hosting", altP, font = { "Arial", 16, bold = true }, position = { 16, 112 }, visible = false;
1382
1383       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1384       {
1385          app.Unlock();
1386          blokusService.Stop();
1387          app.Lock();
1388          hosting = false;
1389          Update(null);
1390          UpdateControlsStates();
1391          return true;
1392       }
1393    };
1394    Button btnStart
1395    {
1396       this, text = "Start Game", altS, font = { "Arial", 16, bold = true }, size = { 124, 32 }, position = { 256, 112 }, visible = false;
1397
1398       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1399       {
1400          if(hosting)
1401          {
1402             StartGame();
1403             UpdateControlsStates();
1404          }
1405          return true;
1406       }
1407    };
1408    Button btnStopGame
1409    {
1410       this, text = "Stop Game", altG, font = { "Arial", 16, bold = true }, position = { 256, 112 }, visible = false;
1411
1412       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1413       {
1414          EndGame();
1415          UpdateControlsStates();
1416          return true;
1417       }
1418    };
1419    ListBox listPlayers
1420    {
1421       this, text = "Players Connected", altD, size = { 236, 84 }, position = { 16, 176 }, visible = false, hasHeader = true;
1422
1423       bool NotifySelect(ListBox listBox, DataRow row, Modifiers mods)
1424       {
1425          UpdateControlsStates();
1426          return true;
1427       }
1428    };
1429    Label lblListPlayers { this, font = { "Tahoma", 8.25f, bold = true }, position = { 16, 152 }, visible = false, labeledWindow = listPlayers };
1430    Button btnKick
1431    {
1432       this, text = "Kick", altK, font = { "Arial", 16, bold = true }, size = { 80, 32 }, position = { 264, 224 }, visible = false;
1433
1434       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1435       {
1436          DataRow row = listPlayers.currentRow;
1437          if(row)
1438             KickPlayer(row.tag);
1439          return true;
1440       }
1441    };
1442    EditBox playerName
1443    {
1444       this, text = "Player Name:", altN, font = { "Arial", 12 }, size = { 132, 24 }, position = { 104, 8 }, contents = "BlokusPlayer"
1445    };
1446    Label lblPlayerName { this, font = { "Tahoma", 8.25f, bold = true }, position = { 16, 16 }, labeledWindow = playerName };
1447 }
1448
1449 CommunicationPanel panel { };
1450
1451 DCOMService blokusService { port = BLOKUS_PORT };