Tweaks for Android
[chess] / src / chess.ec
1 /****************************************************************************
2    CHESS Game
3
4    Copyright (c) 2001 Jerome Jacovella-St-Louis
5    All Rights Reserved.
6    
7    chess.ec - Chess Main Window
8 ****************************************************************************/
9 #ifdef ECERE_STATIC
10 import static "ecere"
11 #else
12 import "ecere"
13 #endif
14 import "chess2D.ec"
15 import "chess3D.ec"
16 import "promotion.ec"
17 import "connect.ec"
18 import "about.ec"
19 import "ai.ec"
20 import "chessutils.ec"
21
22 // --- Definitions ---
23 define APPNAME = "ECERE Chess v0.4";
24 define CHESS_PORT = 7778;
25 define SERVER_COLOR = Black;
26 define CLIENT_COLOR = White;
27
28 // Network Messages
29 enum ChessMessage : byte { NewGame = 1, Position = 2 };
30
31 struct ChessPacket
32 {
33    ChessMessage type;
34    Player player;
35    byte x1,y1,x2,y2;
36    PieceType promotion;
37 };
38
39 ChessApp app;
40
41 class ChessApp : GuiApplication
42 {
43    appName = APPNAME;
44 /*#if defined(__WIN32__)
45    driver = "Direct3D";
46 #else*/
47    driver = "OpenGL";
48 //#endif
49    Chess{};
50
51    void Main()
52    {
53       app = this;
54       GuiApplication::Main();
55    }
56 }
57
58 #ifdef HIGH_DPI
59 define stateWidth = 300;
60 define turnWidth = 150;
61 #else
62 define stateWidth = 200;
63 define turnWidth = 100;
64 #endif
65
66 class Chess : Window
67 {
68    background = gray, hasMenuBar = true, hasStatusBar = true,
69    text = APPNAME,
70 #ifndef __ANDROID__
71    hasClose = true, hasMaximize = true, hasMinimize = true,
72    borderStyle = sizable,
73 #endif
74    anchor = Anchor { left = 0, top = 0, right = 0, bottom = 0 };
75
76    bool hosting, local, ai;
77    Socket sockets[Player];
78
79    ChessService service { port = CHESS_PORT, chess = this };
80
81    MenuItem * driverItems;
82
83    ChessState chessState;
84    
85    StatusField stateField { statusBar, width = stateWidth};
86    StatusField turnField { statusBar, width = turnWidth };
87
88    property ChessState * chessState { get { return &chessState; } }
89
90    // AI Player Threading
91    AIThread aiThread { chess = this };
92
93    // Windows
94    
95    ListBox moveList
96    {
97       parent = this,
98       isActiveClient = true,
99       text = "MoveList",
100       autoScroll = true,
101       hasMinimize = true,
102       borderStyle = sizable,
103       hasVertScroll = true,
104       hasHorzScroll = true,
105       position = Point { x = 445 },
106       size = Size { 200, 450 }
107    };
108
109    Chess2D chess2D
110    {
111       parent = this,
112       text = "2D Chess Board",
113       chessState = &chessState
114    };
115     
116    Chess3D chess3D
117    {
118       parent = this,
119       text = "3D Chess Board",
120       anchor = Anchor { left = 0.5, top = 0.5, right = 0, bottom = 0 },
121       chessState = &chessState,
122       state = maximized
123    };
124
125    // Main Menu
126    menu = Menu {};
127
128    Menu gameMenu    { menu, "Game", g };
129    Menu networkMenu { menu, "Network", n };
130    Menu viewMenu    { menu, "View", v };
131    Menu windowMenu  { menu, "Window", w };
132    MenuDivider      { menu };
133    Menu helpMenu    { menu, "Help", h };
134
135    // Game Menu
136    MenuItem aiItem
137    {
138       gameMenu, "New AI Game\tCtrl+N", n, ctrlN;
139
140       bool NotifySelect(MenuItem selection, Modifiers mods)
141       {
142          if(EndGame())
143          {
144             ai = true;
145             chessState.gameRunning = true;
146
147             chessState.isLocalPlayer[White] = true;
148             chessState.isLocalPlayer[Black] = false;
149
150             EnableMenus();
151          
152             RandomSeed((int)(GetTime() * 10000));
153          
154             NewGame();
155          }
156          return true;
157       }
158    };
159
160    MenuItem localItem
161    {
162       gameMenu, "New Local Game\tCtrl+L", l, ctrlL;
163       bool NotifySelect(MenuItem selection, Modifiers mods)
164       {
165          if(EndGame())
166          {
167             local = true;
168             chessState.gameRunning = true;
169             chessState.isLocalPlayer[White] = true;
170             chessState.isLocalPlayer[Black] = true;
171             EnableMenus();
172             NewGame();
173          }
174          return true;
175       }
176    };
177
178    MenuItem endGameItem
179    {
180       gameMenu, "End Game", e;
181       bool NotifySelect(MenuItem selection, Modifiers mods)
182       {
183          EndGame();
184          return true;
185       }
186    };
187
188    MenuItem { gameMenu, "Exit\tAlt+F4", x, NotifySelect = MenuFileExit };
189
190    // Network Menu
191    MenuItem connectItem
192    {
193       networkMenu, "Connect...", c;
194       bool NotifySelect(MenuItem selection, Modifiers mods)
195       {
196          if(EndGame())
197          {
198             hosting = false;
199             service.Stop();
200             ConnectDialog { master = this }.Modal();
201          }
202          return true;
203       }
204    };
205
206    MenuItem disconnectItem
207    {
208       networkMenu, "Disconnect", d, NotifySelect = endGameItem.NotifySelect
209    };
210
211    MenuItem hostItem
212    {
213       networkMenu, "Host", h;
214       bool NotifySelect(MenuItem selection, Modifiers mods)
215       {
216          if(EndGame())
217          {
218             if(service.Start())
219             {
220                hosting = true;
221                EnableMenus();
222             }
223          }
224          return true;
225       }
226    };
227
228    MenuItem stopItem
229    {
230       networkMenu, "Stop Hosting", s;
231       bool NotifySelect(MenuItem selection, Modifiers mods)
232       {
233          if(EndGame())
234          {
235             hosting = false;
236             service.Stop();
237             EnableMenus();
238          }
239          return true;
240       }
241    };
242
243    // View Menu
244    MenuItem fullScreenItem
245    {
246       //viewMenu, "Full Screen", f, checkable = true;
247       bool NotifySelect(MenuItem selection, Modifiers mods)
248       {
249          app.fullScreen ^= true;
250          SetDriver();
251          anchor = Anchor { left = 0, top = 0, right = 0, bottom = 0 };
252          return true;
253       }
254    };
255
256    bool SetDisplayDriver(MenuItem selection, Modifiers mods)
257    {
258       //app.driver = app.drivers[selection.id];
259       SetDriver();
260       return true;
261    }
262
263    // Window Menu
264    MenuItem { windowMenu, "Next\tF6", n, NotifySelect = MenuWindowNext };
265    MenuItem { windowMenu, "Previous\tShift-F6", p, NotifySelect = MenuWindowPrevious };
266    MenuDivider { windowMenu };
267    MenuItem { windowMenu, "Windows...", w, NotifySelect = MenuWindowWindows };
268
269    //  Help Menu
270    MenuItem aboutItem
271    { 
272       helpMenu, "About...\tF1", a, f1;
273       bool NotifySelect(MenuItem selection, Modifiers mods)
274       {
275          AboutChess { master = this }.Modal();
276          return true;
277       }
278    };
279          
280    // --- Chess Utilities ---
281    bool MakeMove(int x1, int y1, int x2, int y2, PieceType promotion)
282    {
283       bool valid = false;
284       
285       PieceType type = chessState.board[y1][x1].type;
286       Player player = chessState.board[y1][x1].player;
287
288       // Pawn Promotion
289       if(type == Pawn && y2 == ((player == White) ? 7 : 0))
290       {
291          if(chessState.isLocalPlayer[chessState.turn] &&
292             IsMoveValid(x1,y1,x2,y2, chessState, null, true))
293          {
294             chess2D.Update(null);
295             chess3D.Update(null);
296             promotion = (PieceType)Promotion { master = this }.Modal();
297          }
298       }
299       
300       if(StateMakeMove(chessState, x1,y1,x2,y2, promotion, true, null))
301       {
302          valid = true;
303          
304          if(chessState.isLocalPlayer[player] && !local && !ai)
305          {
306             ChessPacket packet
307             { 
308                type = Position,
309                player = player,
310                x1 = (byte)x1,
311                y1 = (byte)y1,
312                x2 = (byte)x2,
313                y2 = (byte)y2,
314                promotion = promotion
315             };
316             sockets[player^(Player)1].Send((byte *)&packet, sizeof(ChessPacket));
317          }
318
319          if(player == Black)
320          {
321             moveList.AddStringf("               %c%c-%c%c",
322                x1+'a',y1+'1', x2+'a',y2+'1');
323          }
324          else
325          {
326             moveList.AddStringf("%3d. %c%c-%c%c",
327                chessState.numMoves/2+1, x1+'a',y1+'1', x2+'a',y2+'1');
328          }
329
330
331          // Update Status Bar
332          {
333             MoveStack stack { };
334             GenerateMoveList(chessState, stack);
335
336             delete stack.moves;
337             
338             if(Check(chessState, chessState.turn, -1, -1))
339             {
340                if(stack.count)
341                {
342                   chessState.state = Check; // TODO: Fix this
343                   stateField.text = "Check!";
344                }
345                else
346                {
347                   chessState.state = CheckMate;
348                   if(chessState.turn == White)
349                      stateField.text = "White are Checkmate.";
350                   else
351                      stateField.text = "Black are Checkmate.";
352                }
353             }
354             else if(!stack.count)
355             {
356                chessState.state = StaleMate;
357                stateField.text = "Stalemate.";
358             }
359             else
360                stateField.text = "";
361
362             // TOFIX: Gets confused with function!
363             if((chessState.state == Check || chessState.state == Normal) &&
364                chessState.isLocalPlayer[chessState.turn] && !local)
365                turnField.text = "Your turn";
366             else
367                turnField.text = "";
368          }
369       }
370    
371       chess2D.Update(null);
372       chess3D.Update(null);
373       return valid;   
374    }
375
376    void ProcessUserMove(int x1, int y1, int x2, int y2)
377    {
378       if(MakeMove(x1, y1, x2, y2, 0))
379       {
380          if(ai)
381             aiThread.Play();
382       }
383
384       chess2D.Update(null);
385
386       chess3D.Update(null);
387    }
388
389    // #define QUEENMATE
390
391    void NewGame()
392    {
393       int x,y;
394
395       moveList.Clear();
396
397       chessState.state = Normal;
398       chessState.numMoves = 0;
399
400       for(y = 0; y <8; y++)
401          for(x = 0; x<8; x++)
402             chessState.board[y][x] = Piece {};
403
404       // WHITE PIECES
405
406    #ifndef QUEENMATE
407       for(x = 0; x<8; x++)
408          chessState.board[1][x] = Piece { White, Pawn };
409       chessState.board[0][0] = chessState.board[0][7] = Piece { White, Rook };
410       chessState.board[0][1] = chessState.board[0][6] = Piece { White, Knight };
411       chessState.board[0][2] = chessState.board[0][5] = Piece { White, Bishop };
412       chessState.board[0][3] = Piece { White, Queen };
413    #endif
414       chessState.board[0][4] = Piece { White, King };
415
416       // BLACK PIECES
417    #ifndef QUEENMATE
418       for(x = 0; x<8; x++)
419          chessState.board[6][x] = Piece { Black, Pawn };
420       chessState.board[7][0] = chessState.board[7][7] = Piece { Black, Rook };
421       chessState.board[7][1] = chessState.board[7][6] = Piece { Black, Knight };
422       chessState.board[7][2] = chessState.board[7][5] = Piece { Black, Bishop };
423    #endif
424       chessState.board[7][3] = Piece { Black, Queen };
425       chessState.board[7][4] = Piece { Black, King };
426
427       // CASTLE STATUS
428    #ifndef QUEENMATE
429       chessState.kingMoved[Black] = chessState.kingMoved[White] = false;
430       chessState.kRookMoved[Black] = chessState.kRookMoved[White] = false;
431       chessState.qRookMoved[Black] = chessState.qRookMoved[White] = false;
432    #else
433       chessState.kRookMoved[Black] = chessState.kRookMoved[White] = true;
434       chessState.qRookMoved[Black] = chessState.qRookMoved[White] = true;
435    #endif
436
437       // KING POSITION
438       chessState.kings[Black].x = chessState.kings[White].x = 4;
439       chessState.kings[Black].y = 7;
440       chessState.kings[White].y = 0;
441
442    #if 0
443       chessState.kings[Black].x = 4;
444       chessState.kings[Black].y = 6;
445       chessState.kings[White].x = 4;
446       chessState.kings[White].y = 0;
447
448       chessState.kingMoved[0] = false;
449       chessState.kingMoved[1] = true;
450       chessState.kRookMoved[0] = false;
451       chessState.qRookMoved[0] = false;
452       chessState.kRookMoved[1] = true;
453       chessState.qRookMoved[1] = true;
454
455       chessState.board[0][0] = Piece { White, Rook };
456       chessState.board[0][4] = Piece { White, King };
457       chessState.board[0][7] = Piece { White, Rook };
458       chessState.board[1][0] = Piece { White, Pawn };
459       chessState.board[1][2] = Piece { White, Pawn };
460       chessState.board[1][5] = Piece { White, Pawn };
461       chessState.board[1][6] = Piece { White, Pawn };
462       chessState.board[1][7] = Piece { White, Pawn };
463       chessState.board[2][1] = Piece { White, Pawn };
464       chessState.board[2][2] = Piece { White, Knight };
465       chessState.board[4][3] = Piece { White, Pawn };
466       chessState.board[5][6] = Piece { White, Bishop };
467
468       chessState.board[5][0] = Piece { Black, Pawn };
469       chessState.board[5][1] = Piece { Black, Pawn };
470       chessState.board[5][3] = Piece { Black, Knight };
471       chessState.board[5][5] = Piece { Black, Pawn };
472       chessState.board[5][7] = Piece { Black, Bishop };
473       chessState.board[6][0] = Piece { White, Rook };
474       chessState.board[6][2] = Piece { Black, Pawn };
475       chessState.board[6][3] = Piece { Black, Pawn };
476       chessState.board[6][4] = Piece { Black, King };
477       chessState.board[7][1] = Piece { Black, Knight };
478       chessState.board[7][2] = Piece { Black, Bishop };
479    #endif
480       chessState.turn = White;
481
482       chessState.castled[White] = 
483       chessState.castled[Black] = false;
484
485       // EN PASSANT STATUS
486       chessState.enPassant.x = -1;
487       chessState.enPassant.y = -1;
488
489       chessState.pieceCount[White][Pawn]   = chessState.pieceCount[Black][Pawn] = 8;
490       chessState.pieceCount[White][Knight] = chessState.pieceCount[Black][Knight] = 2;
491       chessState.pieceCount[White][Bishop] = chessState.pieceCount[Black][Bishop] = 2;
492       chessState.pieceCount[White][Rook]   = chessState.pieceCount[Black][Rook] = 2;
493       chessState.pieceCount[White][Queen]  = chessState.pieceCount[Black][Queen] = 1;
494       chessState.pieceCount[White][King]   = chessState.pieceCount[Black][King] = 1;
495
496       chessState.materialValue[White] =
497          chessState.pieceCount[White][Pawn]   * materialValues[Pawn] +
498          chessState.pieceCount[White][Knight] * materialValues[Knight] +
499          chessState.pieceCount[White][Bishop] * materialValues[Bishop] +
500          chessState.pieceCount[White][Rook]   * materialValues[Rook] +
501          chessState.pieceCount[White][Queen]  * materialValues[Queen];
502       chessState.materialValue[Black] =
503          chessState.pieceCount[Black][Pawn]   * materialValues[Pawn] +
504          chessState.pieceCount[Black][Knight] * materialValues[Knight] +
505          chessState.pieceCount[Black][Bishop] * materialValues[Bishop] +
506          chessState.pieceCount[Black][Rook]   * materialValues[Rook] +
507          chessState.pieceCount[Black][Queen]  * materialValues[Queen];
508
509       chess2D.Update(null);
510       chess3D.Update(null);
511
512       if(chessState.isLocalPlayer[chessState.turn] && !local)
513          turnField.text = "Your turn";
514       stateField.text = "";
515
516    /*
517       // 1
518       MakeMoveChar('e',2, 'e',4);
519       MakeMoveChar('a',7, 'a',6);
520       // 2
521       MakeMoveChar('d',2, 'd',4);
522       MakeMoveChar('f',7, 'f',5);
523       // 3
524       MakeMoveChar('e',4, 'f',5);
525       MakeMoveChar('b',8, 'c',6);
526       // 4
527       MakeMoveChar('d',4, 'd',5);
528       MakeMoveChar('c',6, 'e',5);
529       // 5
530       MakeMoveChar('f',2, 'f',4);
531       MakeMoveChar('e',5, 'f',7);
532       // 6
533       MakeMoveChar('g',1, 'f',3);
534       MakeMoveChar('c',7, 'c',5);
535       // 7
536       MakeMoveChar('c',2, 'c',4);
537       MakeMoveChar('g',7, 'g',6);
538       // 8
539       MakeMoveChar('f',5, 'g',6);
540       MakeMoveChar('f',7, 'd',6);
541       // 9
542       MakeMoveChar('g',6, 'h',7);
543       MakeMoveChar('g',8, 'h',6);
544       // 10
545       MakeMoveChar('f',1, 'd',3);
546       MakeMoveChar('d',6, 'f',7);
547       // 11
548       MakeMoveChar('c',1, 'd',2);
549       MakeMoveChar('d',8, 'b',6);
550       // 12
551       MakeMoveChar('d',2, 'c',3);
552       MakeMoveChar('b',6, 'd',6);
553       // 13
554       MakeMoveChar('c',3, 'h',8);
555       MakeMoveChar('f',7, 'h',8);
556       // 14
557       MakeMoveChar('d',1, 'd',2);
558       MakeMoveChar('e',7, 'e',6);      
559    */
560    }
561
562    void MakeMoveChar(char x1, int y1, char x2, int y2)
563    {
564       MakeMove(x1 - 'a', y1 - 1, x2 - 'a', y2 - 1, Queen);
565    }
566    
567    void EnableMenus()
568    {
569       stopItem.disabled = !hosting;
570       disconnectItem.disabled = !sockets[SERVER_COLOR] && !sockets[CLIENT_COLOR];
571       endGameItem.disabled = !chessState.gameRunning;
572       hostItem.disabled = hosting;
573    }
574
575    void SetDriver()
576    {
577       int c;
578       for(c = 0; c<app.numDrivers; c++)
579          if(!strcmp(app.drivers[c], app.driver))
580          {
581             driverItems[c].checked = true;
582             break;
583          }
584    }
585
586    // --- Chess Window Class ---
587    bool OnCreate()
588    {
589       int c;
590
591       driverItems = new MenuItem[app.numDrivers];
592       for(c = 0; c<app.numDrivers; c++)
593       {
594          driverItems[c] = MenuItem { viewMenu, app.drivers[c], NotifySelect = SetDisplayDriver };
595          driverItems[c].id = c;
596          driverItems[c].isRadio = true;         
597       }
598       // this.SetPalette(palette, true);
599
600       SetDriver();
601       NewGame();
602       EnableMenus();
603
604       return true;
605    }
606
607    bool EndGame()
608    {
609       if(chessState.gameRunning && 
610          (chessState.state == Normal || chessState.state == Check))
611       {
612          if(MessageBox { type = okCancel, contents = "Quit current game?", 
613             master = this, text = "ECERE Chess" }.Modal() == cancel)
614             return false;   
615       }
616       if(sockets[SERVER_COLOR])
617          sockets[SERVER_COLOR].Disconnect(0);
618       else if(sockets[CLIENT_COLOR])
619          sockets[CLIENT_COLOR].Disconnect(0);
620       else if(local || ai)
621       {
622          if(ai) aiThread.Abort();
623          local = ai = false;
624          chessState.gameRunning = false;
625       }
626       EnableMenus();
627
628       turnField.text = "";
629       stateField.text = "";
630
631       return true;
632    }
633
634    bool OnClose(bool parentClosing)
635    {
636       return EndGame();
637    }
638
639    void OnDestroy()
640    {
641       delete sockets[Black];
642       delete sockets[White];
643       delete driverItems;
644    }
645             
646    void Connect(char * address)
647    {
648       ChessSocket socket { chess = this };
649       if(socket.Connect(address, CHESS_PORT))
650       {
651          sockets[SERVER_COLOR] = socket;
652          EnableMenus();
653
654          chessState.isLocalPlayer[CLIENT_COLOR] = true;
655          chessState.isLocalPlayer[SERVER_COLOR] = false;
656       }
657    }
658 }
659
660 // --- Chess Communications ---
661 class ChessSocket : Socket
662 {
663    Chess chess;
664    property Chess chess { set { chess = value; } }
665
666    void OnDisconnect(int code)
667    {
668       if(this == chess.sockets[CLIENT_COLOR])
669       {
670          chess.sockets[CLIENT_COLOR] = null;
671          chess.chessState->gameRunning = false;
672          chess.turnField.text = "";
673          chess.stateField.text = "";
674       }
675       else if(this == chess.sockets[SERVER_COLOR])
676       {
677          chess.sockets[SERVER_COLOR] = null;
678          chess.chessState->gameRunning = false;
679          chess.turnField.text = "";
680          chess.stateField.text = "";
681       }
682
683       chess.EnableMenus();
684       chess.chess2D.Update(null);
685       chess.chess3D.Update(null);
686    }
687    
688    uint OnReceive(const byte * buffer, uint count)
689    {
690       if(count >= sizeof(ChessPacket))
691       {
692          ChessPacket packet = *(ChessPacket *)buffer;
693          switch(packet.type)
694          {
695             case Position:
696                chess.MakeMove(packet.x1, packet.y1, packet.x2, packet.y2, packet.promotion);
697                chess.Activate();
698                break;
699             case NewGame:
700                chess.chessState->gameRunning = true;
701                chess.NewGame();
702                break;
703          }
704          return sizeof(ChessPacket);
705       }
706       return 0;
707    }
708
709    void OnConnect()
710    {
711       chess.sockets[SERVER_COLOR] = this;
712       chess.chessState->gameRunning = true;
713       chess.EnableMenus();
714       chess.NewGame();
715    }
716 }
717
718 class ChessService : Service
719 {
720    Chess chess;
721    property Chess chess { set { chess = value; } }
722    
723    void OnAccept()
724    {
725       if(!chess.chessState->gameRunning)
726       {
727          ChessPacket packet { type = NewGame };
728
729          chess.sockets[CLIENT_COLOR] = ChessSocket { this, chess = chess };
730          chess.sockets[CLIENT_COLOR].Send((byte *)&packet, sizeof(ChessPacket));
731
732          chess.chessState->isLocalPlayer[SERVER_COLOR] = true;
733          chess.chessState->isLocalPlayer[CLIENT_COLOR] = false;
734
735          chess.NewGame();
736          chess.chessState->gameRunning = true;
737          chess.EnableMenus();
738          chess.chess2D.Update(null);
739          chess.chess3D.Update(null);
740       }
741    }
742 }