From: Jerome St-Louis Date: Tue, 17 May 2011 04:22:20 +0000 (-0400) Subject: Initial Git commit X-Git-Url: http://ecere.com/cgi-bin/gitweb.cgi?p=chess;a=commitdiff_plain;h=4c30b8914ddddd5381ece38f84c1c42399dd34c6 Initial Git commit --- 4c30b8914ddddd5381ece38f84c1c42399dd34c6 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..61b4682 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ + Copyright (c) 1996-2007, Jerome Jacovella-St-Louis + Copyright (c) 2005-2007, Ecere Corporation + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Ecere Corporation nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/chess.epj b/chess.epj new file mode 100644 index 0000000..843f10f --- /dev/null +++ b/chess.epj @@ -0,0 +1,70 @@ +{ + "Version" : 0.2, + "ModuleName" : "chess", + "Options" : { + "TargetType" : "Executable", + "TargetFileName" : "chess", + "Libraries" : [ + "ecere" + ] + }, + "Configurations" : [ + { + "Name" : "Debug", + "Options" : { + "Debug" : true, + "Console" : true + } + }, + { + "Name" : "Release", + "Options" : { + "Optimization" : "Speed" + } + }, + { + "Name" : "MemoryGuard", + "Options" : { + "Debug" : true, + "MemoryGuard" : true, + "Console" : true + } + } + ], + "Files" : [ + { + "Folder" : "src", + "Files" : [ + "about.ec", + "ai.ec", + "chess.ec", + "chess2D.ec", + "chess3D.ec", + "chessutils.ec", + "connect.ec", + "promotion.ec" + ] + } + ], + "ResourcesPath" : "res", + "Resources" : [ + "aboutPic.jpg", + "blackBishop.png", + "blackKing.png", + "blackKnight.png", + "blackPawn.png", + "blackQueen.png", + "blackRook.png", + "board.jpg", + "bthr.jpg", + "darkwood.jpg", + "lightwo1.jpg", + "whiteBishop.png", + "whiteKing.png", + "whiteKnight.png", + "whitePawn.png", + "whiteQueen.png", + "whiteRook.png", + "chessSet.3ds" + ] +} \ No newline at end of file diff --git a/res/aboutPic.jpg b/res/aboutPic.jpg new file mode 100644 index 0000000..6a0a1b3 Binary files /dev/null and b/res/aboutPic.jpg differ diff --git a/res/blackBishop.png b/res/blackBishop.png new file mode 100644 index 0000000..f457c92 Binary files /dev/null and b/res/blackBishop.png differ diff --git a/res/blackKing.png b/res/blackKing.png new file mode 100644 index 0000000..b621783 Binary files /dev/null and b/res/blackKing.png differ diff --git a/res/blackKnight.png b/res/blackKnight.png new file mode 100644 index 0000000..85b3c2a Binary files /dev/null and b/res/blackKnight.png differ diff --git a/res/blackPawn.png b/res/blackPawn.png new file mode 100644 index 0000000..037ea60 Binary files /dev/null and b/res/blackPawn.png differ diff --git a/res/blackQueen.png b/res/blackQueen.png new file mode 100644 index 0000000..408cb2f Binary files /dev/null and b/res/blackQueen.png differ diff --git a/res/blackRook.png b/res/blackRook.png new file mode 100644 index 0000000..4ddbd60 Binary files /dev/null and b/res/blackRook.png differ diff --git a/res/board.jpg b/res/board.jpg new file mode 100644 index 0000000..bb755bb Binary files /dev/null and b/res/board.jpg differ diff --git a/res/bthr.jpg b/res/bthr.jpg new file mode 100644 index 0000000..56dff58 Binary files /dev/null and b/res/bthr.jpg differ diff --git a/res/chessSet.3ds b/res/chessSet.3ds new file mode 100644 index 0000000..c69a86a Binary files /dev/null and b/res/chessSet.3ds differ diff --git a/res/darkwood.jpg b/res/darkwood.jpg new file mode 100644 index 0000000..8853e37 Binary files /dev/null and b/res/darkwood.jpg differ diff --git a/res/lightwo1.jpg b/res/lightwo1.jpg new file mode 100644 index 0000000..437e05d Binary files /dev/null and b/res/lightwo1.jpg differ diff --git a/res/whiteBishop.png b/res/whiteBishop.png new file mode 100644 index 0000000..75eb4ec Binary files /dev/null and b/res/whiteBishop.png differ diff --git a/res/whiteKing.png b/res/whiteKing.png new file mode 100644 index 0000000..0326b3a Binary files /dev/null and b/res/whiteKing.png differ diff --git a/res/whiteKnight.png b/res/whiteKnight.png new file mode 100644 index 0000000..8e0062d Binary files /dev/null and b/res/whiteKnight.png differ diff --git a/res/whitePawn.png b/res/whitePawn.png new file mode 100644 index 0000000..5695681 Binary files /dev/null and b/res/whitePawn.png differ diff --git a/res/whiteQueen.png b/res/whiteQueen.png new file mode 100644 index 0000000..5e2849f Binary files /dev/null and b/res/whiteQueen.png differ diff --git a/res/whiteRook.png b/res/whiteRook.png new file mode 100644 index 0000000..1a2b3d2 Binary files /dev/null and b/res/whiteRook.png differ diff --git a/src/about.ec b/src/about.ec new file mode 100644 index 0000000..aeee025 --- /dev/null +++ b/src/about.ec @@ -0,0 +1,30 @@ +import "chess.ec" + +class AboutChess : Window +{ + background = black; + foreground = white, size = Size { 440, 200 }, hasClose = true, + text = APPNAME; + + Button ok + { + this, isDefault = true, text = "OK", + anchor = { bottom = 10 }, size = { 80 }; + + NotifyClicked = ButtonCloseDialog; + }; + + Picture picture + { + this, position = { 0, 10 }, image = { ":aboutPic.jpg" }, + size = { 180, 130 } + }; + + void OnRedraw(Surface surface) + { + surface.WriteTextf(200, 30, "Copyright (c) 1996-2005"); + surface.WriteTextf(200, 50, " Jerome Jacovella-St-Louis"); + surface.WriteTextf(200, 70, "Models Copyright (c) 2004"); + surface.WriteTextf(200, 90, " Gaetan Loyer"); + } +} diff --git a/src/ai.ec b/src/ai.ec new file mode 100644 index 0000000..2e4bcd4 --- /dev/null +++ b/src/ai.ec @@ -0,0 +1,391 @@ +import "chess.ec" + +struct MoveStack +{ + ChessMove * moves; + int size, count; +}; + +define MAXDEPTH = 4; +// define MAXDEPTH = 6; +define MAXDEPTH_PASS2 = 50; + +static MoveStack moveStack[MAXDEPTH_PASS2]; + +void AddMoveToList(MoveStack stack, ChessState state, PieceType type, Player player, + int x1, int y1, int x2, int y2) +{ + if(IsMoveValid(x1,y1,x2,y2, state, null, true)) + { + ChessMove * move; + + if(stack.count + 1 > stack.size) + { + stack.size += 10000; + stack.moves = renew stack.moves ChessMove[stack.size]; + } + + move = &stack.moves[stack.count++]; + + move->player = player; + move->type = type; + move->x1 = x1; + move->y1 = y1; + move->x2 = x2; + move->y2 = y2; + // For now queen only... + move->promotion = Queen; + } +} + +void GeneratePieceMoveList(ChessState state, int x, int y, MoveStack stack) +{ + Piece content = state.board[y][x]; + Player player = content.player; + PieceType type = content.type; + int x2,y2; + + switch(type) + { + case Pawn: + { + int direction = (player == White) ? 1 : -1; + + AddMoveToList(stack, state, type, player, x,y,x,y+direction); + + if((player == White && y == 1) || + (player == Black && y == 6)) + AddMoveToList(stack, state, type, player, x,y,x,y+direction*2); + + // Capturing Move (Including en passant) + if(x > 0) + AddMoveToList(stack, state, type, player, x,y,x-1,y+direction); + if(x < 7) + AddMoveToList(stack, state, type, player, x,y,x+1,y+direction); + break; + } + case Knight: + AddMoveToList(stack, state, type, player, x,y, x+1,y+2); + AddMoveToList(stack, state, type, player, x,y, x+2,y+1); + AddMoveToList(stack, state, type, player, x,y, x+2,y-1); + AddMoveToList(stack, state, type, player, x,y, x+1,y-2); + AddMoveToList(stack, state, type, player, x,y, x-1,y-2); + AddMoveToList(stack, state, type, player, x,y, x-2,y-1); + AddMoveToList(stack, state, type, player, x,y, x-2,y+1); + AddMoveToList(stack, state, type, player, x,y, x-1,y+2); + break; + case Bishop: + { + for(x2 = 0; x2<8; x2++) + { + if(x2 != x) + { + y2 = y + Abs(x2 - x); + if(y2 < 8) + AddMoveToList(stack, state, type, player, x,y,x2,y2); + y2 = y - Abs(x2 - x); + if(y2 >= 0) + AddMoveToList(stack, state, type, player, x,y,x2,y2); + } + } + break; + } + case Rook: + for(x2 = 0; x2<8; x2++) + { + if(x2 != x) + { + AddMoveToList(stack, state, type, player, x,y,x2,y); + } + } + for(y2 = 0; y2<8; y2++) + { + if(y2 != y) + { + AddMoveToList(stack, state, type, player, x,y,x,y2); + } + } + break; + case Queen: + for(x2 = 0; x2<8; x2++) + { + if(x2 != x) + { + y2 = y + Abs(x2 - x); + if(y2 < 8) + AddMoveToList(stack, state, type, player, x,y,x2,y2); + y2 = y - Abs(x2 - x); + if(y2 >= 0) + AddMoveToList(stack, state, type, player, x,y,x2,y2); + } + } + for(x2 = 0; x2<8; x2++) + { + if(x2 != x) + { + AddMoveToList(stack, state, type, player, x,y,x2,y); + } + } + for(y2 = 0; y2<8; y2++) + { + if(y2 != y) + { + AddMoveToList(stack, state, type, player, x,y,x,y2); + } + } + break; + case King: + AddMoveToList(stack, state, type, player, x,y, x,y+1); + AddMoveToList(stack, state, type, player, x,y, x+1,y+1); + AddMoveToList(stack, state, type, player, x,y, x+1,y); + AddMoveToList(stack, state, type, player, x,y, x+1,y-1); + AddMoveToList(stack, state, type, player, x,y, x,y-1); + AddMoveToList(stack, state, type, player, x,y, x-1,y-1); + AddMoveToList(stack, state, type, player, x,y, x-1,y); + AddMoveToList(stack, state, type, player, x,y, x-1,y+1); + + // Castle + AddMoveToList(stack, state, type, player, x,y, x-2,y); + AddMoveToList(stack, state, type, player, x,y, x+2,y); + break; + } +} + +void GenerateMoveList(ChessState state, MoveStack stack) +{ + int x,y; + + stack.count = 0; + for(x = 0; x<8; x++) + for(y = 0; y<8; y++) + { + Piece content = state.board[y][x]; + if(content && content.player == state.turn) + GeneratePieceMoveList(state, x,y, stack); + } +} + +int EvaluateMaterial(Piece board[8][8], Player turn) +{ + int x,y; + int materialMe = 0, materialOpp = 0; + + for(y = 0; y<8; y++) + for(x = 0; x<8; x++) + { + Piece content = board[y][x]; + if(content != Piece {}) + { + Player player = content.player; + PieceType piece = content.type; + + if(player == turn) + materialMe += materialValues[piece]; + else + materialOpp += materialValues[piece]; + } + } + return materialMe - materialOpp; +} + +int EvaluatePosition(ChessState state, Player turn) +{ + int position = 0; + + // To help the mate... + if(state.materialValue[White] < 7) + { + if((state.kings[White].x >= 3 && state.kings[White].x <= 4) && + (state.kings[White].y >= 3 && state.kings[White].y <= 4)) + { + position -= 3; + } + else if((state.kings[White].x >= 2 && state.kings[White].x <= 5) && + (state.kings[White].y >= 2 && state.kings[White].y <= 5)) + { + position -= 2; + } + else if((state.kings[White].x >= 1 && state.kings[White].x <= 6) && + (state.kings[White].y >= 1 && state.kings[White].y <= 6)) + { + position -= 1; + } + } + + // Castle + if(state.castled[White]) position -= 15; + if(state.castled[Black]) position += 15; + return position; +} + +static int CompareGreater(const ChessMove a, const ChessMove b) +{ + if(a.rating > b.rating) return 1; + else if(a.rating < b.rating) return -1; + else + { + if(a.count < b.count) return 1; + else if(a.count > b.count) return -1; + else + return 0; + } +} + +static int CompareSmaller(const ChessMove a, const ChessMove b) +{ + if(a.rating < b.rating) return 1; + else if(a.rating > b.rating) return -1; + else + { + if(a.count > b.count) return 1; + else if(a.count < b.count) return -1; + else + return 0; + } +} + +bool FindMove(ChessState startState, int depth, int *maxDepth, ChessMove bestMove, int startRating, int * returnedRating, + int startCount, int * returnedCount, bool * abort) +{ + Player player = startState.turn; + MoveStack * stack = &moveStack[depth]; + int bestRating = (player == Black) ? MININT : 200000; + int c; + int bestCount = MAXINT; + int maxShots = stack->count; + + // Stop AI + if(*abort) return false; + + GenerateMoveList(startState, stack); + if(!stack->count && !Check(startState, player, startState.kings[player].x, startState.kings[player].y)) + bestRating = 0; + + if(!stack->count) + bestCount = startCount; + + maxShots = stack->count; + for(c = 0; cmoves[c]; + int newRating, newCount = MAXINT; + int delta = 0; + ChessState state = startState; + + StateMakeMove(&state, move->x1, move->y1, move->x2, move->y2, move->promotion, false, &delta); + if(player == White) + delta *= -1; + + if(Abs(delta) < 1000 && (depth+1 < *maxDepth)) + { + FindMove(&state, depth+1, maxDepth, null, startRating + delta, &newRating, startCount+1, &newCount, abort); + } + else + { + int position; + position = EvaluatePosition(&state, player); + newRating = startRating + delta + position; + + if((player == Black) ? (newRating > bestRating) : (newRating < bestRating)) + newCount = startCount+1; + } + + move->rating = newRating; + move->count = newCount; + + if((player == Black) ? (newRating >= bestRating) : (newRating <= bestRating)) + { + bestRating = newRating; + if(move->count < bestCount) + bestCount = move->count; + } + } + stack->count = maxShots; + + // Pass 2 + if(bestMove && stack->count) + { + int numGoodMoves; + if(player == Black) + qsort(stack->moves, stack->count, sizeof(ChessMove), CompareSmaller); + else + qsort(stack->moves, stack->count, sizeof(ChessMove), CompareGreater); + + for(numGoodMoves = 0; numGoodMovescount; numGoodMoves++) + { + if(stack->moves[numGoodMoves].rating != bestRating) + break; + if(stack->moves[numGoodMoves].count != bestCount) + break; + } + + c = GetRandom(0, numGoodMoves-1); + bestMove = stack->moves[c]; + } + if(returnedRating) + *returnedRating = bestRating; + if(returnedCount) + *returnedCount = bestCount; + Sleep(0); + + return stack->count > 0; +} + +class AIThread : Thread +{ + Chess chess; + bool aiMoveResult; + bool abortAI; + ChessMove aiMove; + + Timer aiTimer + { + this, 0.1; + + bool DelayExpired() + { + if(aiMoveResult) + { + Wait(); + chess.MakeMove(aiMove.x1, aiMove.y1, aiMove.x2, aiMove.y2, aiMove.promotion); + aiMoveResult = false; + aiTimer.Stop(); + } + return true; + } + }; + + void Play() + { + abortAI = false; + app.UpdateDisplay(); + Create(); + aiTimer.Start(); + } + + void Abort() + { + abortAI = true; + Wait(); + aiTimer.Stop(); + aiMoveResult = false; + } + + ChessState * chessState; + + uint Main() + { + ChessState * chessState = chess.chessState; + int startRating = EvaluateMaterial(chessState->board, chessState->turn); + int depth = MAXDEPTH; + + RandomSeed((int)(GetTime() * 1000)); + aiMoveResult = FindMove(chessState, 0, &depth, &aiMove, startRating, null, 0, null, &abortAI); + + return 0; + } + +public: + property Chess chess { set { chess = value; } } + property ChessState * chessState { set { chessState = value; } } +} diff --git a/src/chess.ec b/src/chess.ec new file mode 100644 index 0000000..e86b27e --- /dev/null +++ b/src/chess.ec @@ -0,0 +1,731 @@ +/**************************************************************************** + CHESS Game + + Copyright (c) 2001 Jerome Jacovella-St-Louis + All Rights Reserved. + + chess.ec - Chess Main Window +****************************************************************************/ +#ifdef ECERE_STATIC +import static "ecere" +#else +import "ecere" +#endif +import "chess2D.ec" +import "chess3D.ec" +import "promotion.ec" +import "connect.ec" +import "about.ec" +import "ai.ec" +import "chessutils.ec" + +// --- Definitions --- +define APPNAME = "ECERE Chess v0.4"; +define CHESS_PORT = 7778; +define SERVER_COLOR = Black; +define CLIENT_COLOR = White; + +// Network Messages +enum ChessMessage : byte { NewGame = 1, Position = 2 }; + +struct ChessPacket +{ + ChessMessage type; + Player player; + byte x1,y1,x2,y2; + PieceType promotion; +}; + +ChessApp app; + +class ChessApp : GuiApplication +{ + appName = APPNAME; +#if defined(__WIN32__) + driver = "Direct3D"; +#else + driver = "OpenGL"; +#endif + Chess{}; + + void Main() + { + app = this; + GuiApplication::Main(); + } +} + +class Chess : Window +{ + background = gray, hasMenuBar = true, hasStatusBar = true, + text = APPNAME, hasClose = true, hasMaximize = true, hasMinimize = true, + borderStyle = sizable, hasClose = true, + anchor = Anchor { left = 0, top = 0, right = 0, bottom = 0 }; + + bool hosting, local, ai; + Socket sockets[Player]; + + ChessService service { port = CHESS_PORT, chess = this }; + + MenuItem * driverItems; + + ChessState chessState; + + StatusField stateField { statusBar, width = 200 }; + StatusField turnField { statusBar, width = 100 }; + + property ChessState * chessState { get { return &chessState; } } + + // AI Player Threading + AIThread aiThread { chess = this }; + + // Windows + + ListBox moveList + { + parent = this, + isActiveClient = true, + text = "MoveList", + autoScroll = true, + hasMinimize = true, + borderStyle = sizable, + hasVertScroll = true, + hasHorzScroll = true, + position = Point { x = 445 }, + size = Size { 200, 450 } + }; + + Chess2D chess2D + { + parent = this, + text = "2D Chess Board", + chessState = &chessState + }; + + Chess3D chess3D + { + parent = this, + text = "3D Chess Board", + anchor = Anchor { left = 0.5, top = 0.5, right = 0, bottom = 0 }, + chessState = &chessState, + state = maximized + }; + + // Main Menu + menu = Menu {}; + + Menu gameMenu { menu, "Game", g }; + Menu networkMenu { menu, "Network", n }; + Menu viewMenu { menu, "View", v }; + Menu windowMenu { menu, "Window", w }; + MenuDivider { menu }; + Menu helpMenu { menu, "Help", h }; + + // Game Menu + MenuItem aiItem + { + gameMenu, "New AI Game\tCtrl+N", n, ctrlN; + + bool NotifySelect(MenuItem selection, Modifiers mods) + { + if(EndGame()) + { + ai = true; + chessState.gameRunning = true; + + chessState.isLocalPlayer[White] = true; + chessState.isLocalPlayer[Black] = false; + + EnableMenus(); + + RandomSeed((int)(GetTime() * 10000)); + + NewGame(); + } + return true; + } + }; + + MenuItem localItem + { + gameMenu, "New Local Game\tCtrl+L", l, ctrlL; + bool NotifySelect(MenuItem selection, Modifiers mods) + { + if(EndGame()) + { + local = true; + chessState.gameRunning = true; + chessState.isLocalPlayer[White] = true; + chessState.isLocalPlayer[Black] = true; + EnableMenus(); + NewGame(); + } + return true; + } + }; + + MenuItem endGameItem + { + gameMenu, "End Game", e; + bool NotifySelect(MenuItem selection, Modifiers mods) + { + EndGame(); + return true; + } + }; + + MenuItem { gameMenu, "Exit\tAlt+F4", x, NotifySelect = MenuFileExit }; + + // Network Menu + MenuItem connectItem + { + networkMenu, "Connect...", c; + bool NotifySelect(MenuItem selection, Modifiers mods) + { + if(EndGame()) + { + hosting = false; + service.Stop(); + ConnectDialog { master = this }.Modal(); + } + return true; + } + }; + + MenuItem disconnectItem + { + networkMenu, "Disconnect", d, NotifySelect = endGameItem.NotifySelect + }; + + MenuItem hostItem + { + networkMenu, "Host", h; + bool NotifySelect(MenuItem selection, Modifiers mods) + { + if(EndGame()) + { + if(service.Start()) + { + hosting = true; + EnableMenus(); + } + } + return true; + } + }; + + MenuItem stopItem + { + networkMenu, "Stop Hosting", s; + bool NotifySelect(MenuItem selection, Modifiers mods) + { + if(EndGame()) + { + hosting = false; + service.Stop(); + EnableMenus(); + } + return true; + } + }; + + // View Menu + MenuItem fullScreenItem + { + //viewMenu, "Full Screen", f, checkable = true; + bool NotifySelect(MenuItem selection, Modifiers mods) + { + app.fullScreen ^= true; + SetDriver(); + anchor = Anchor { left = 0, top = 0, right = 0, bottom = 0 }; + return true; + } + }; + + bool SetDisplayDriver(MenuItem selection, Modifiers mods) + { + //app.driver = app.drivers[selection.id]; + SetDriver(); + return true; + } + + // Window Menu + MenuItem { windowMenu, "Next\tF6", n, NotifySelect = MenuWindowNext }; + MenuItem { windowMenu, "Previous\tShift-F6", p, NotifySelect = MenuWindowPrevious }; + MenuDivider { windowMenu }; + MenuItem { windowMenu, "Windows...", w, NotifySelect = MenuWindowWindows }; + + // Help Menu + MenuItem aboutItem + { + helpMenu, "About...\tF1", a, f1; + bool NotifySelect(MenuItem selection, Modifiers mods) + { + AboutChess { master = this }.Modal(); + return true; + } + }; + + // --- Chess Utilities --- + bool MakeMove(int x1, int y1, int x2, int y2, PieceType promotion) + { + bool valid = false; + + PieceType type = chessState.board[y1][x1].type; + Player player = chessState.board[y1][x1].player; + + // Pawn Promotion + if(type == Pawn && y2 == ((player == White) ? 7 : 0)) + { + if(chessState.isLocalPlayer[chessState.turn] && + IsMoveValid(x1,y1,x2,y2, chessState, null, true)) + { + chess2D.Update(null); + chess3D.Update(null); + promotion = (PieceType)Promotion { master = this }.Modal(); + } + } + + if(StateMakeMove(chessState, x1,y1,x2,y2, promotion, true, null)) + { + valid = true; + + if(chessState.isLocalPlayer[player] && !local && !ai) + { + ChessPacket packet + { + type = Position, + player = player, + x1 = (byte)x1, + y1 = (byte)y1, + x2 = (byte)x2, + y2 = (byte)y2, + promotion = promotion + }; + sockets[player^(Player)1].Send((byte *)&packet, sizeof(ChessPacket)); + } + + if(player == Black) + { + moveList.AddStringf(" %c%c-%c%c", + x1+'a',y1+'1', x2+'a',y2+'1'); + } + else + { + moveList.AddStringf("%3d. %c%c-%c%c", + chessState.numMoves/2+1, x1+'a',y1+'1', x2+'a',y2+'1'); + } + + + // Update Status Bar + { + MoveStack stack { }; + GenerateMoveList(chessState, stack); + + delete stack.moves; + + if(Check(chessState, chessState.turn, -1, -1)) + { + if(stack.count) + { + chessState.state = Check; // TODO: Fix this + stateField.text = "Check!"; + } + else + { + chessState.state = CheckMate; + if(chessState.turn == White) + stateField.text = "White are Checkmate."; + else + stateField.text = "Black are Checkmate."; + } + } + else if(!stack.count) + { + chessState.state = StaleMate; + stateField.text = "Stalemate."; + } + else + stateField.text = ""; + + // TOFIX: Gets confused with function! + if((chessState.state == Check || chessState.state == Normal) && + chessState.isLocalPlayer[chessState.turn] && !local) + turnField.text = "Your turn"; + else + turnField.text = ""; + } + } + + chess2D.Update(null); + chess3D.Update(null); + return valid; + } + + void ProcessUserMove(int x1, int y1, int x2, int y2) + { + if(MakeMove(x1, y1, x2, y2, 0)) + { + if(ai) + aiThread.Play(); + } + + chess2D.Update(null); + + chess3D.Update(null); + } + + // #define QUEENMATE + + void NewGame() + { + int x,y; + + moveList.Clear(); + + chessState.state = Normal; + chessState.numMoves = 0; + + for(y = 0; y <8; y++) + for(x = 0; x<8; x++) + chessState.board[y][x] = Piece {}; + + // WHITE PIECES + + #ifndef QUEENMATE + for(x = 0; x<8; x++) + chessState.board[1][x] = Piece { White, Pawn }; + chessState.board[0][0] = chessState.board[0][7] = Piece { White, Rook }; + chessState.board[0][1] = chessState.board[0][6] = Piece { White, Knight }; + chessState.board[0][2] = chessState.board[0][5] = Piece { White, Bishop }; + chessState.board[0][3] = Piece { White, Queen }; + #endif + chessState.board[0][4] = Piece { White, King }; + + // BLACK PIECES + #ifndef QUEENMATE + for(x = 0; x<8; x++) + chessState.board[6][x] = Piece { Black, Pawn }; + chessState.board[7][0] = chessState.board[7][7] = Piece { Black, Rook }; + chessState.board[7][1] = chessState.board[7][6] = Piece { Black, Knight }; + chessState.board[7][2] = chessState.board[7][5] = Piece { Black, Bishop }; + #endif + chessState.board[7][3] = Piece { Black, Queen }; + chessState.board[7][4] = Piece { Black, King }; + + // CASTLE STATUS + #ifndef QUEENMATE + chessState.kingMoved[Black] = chessState.kingMoved[White] = false; + chessState.kRookMoved[Black] = chessState.kRookMoved[White] = false; + chessState.qRookMoved[Black] = chessState.qRookMoved[White] = false; + #else + chessState.kRookMoved[Black] = chessState.kRookMoved[White] = true; + chessState.qRookMoved[Black] = chessState.qRookMoved[White] = true; + #endif + + // KING POSITION + chessState.kings[Black].x = chessState.kings[White].x = 4; + chessState.kings[Black].y = 7; + chessState.kings[White].y = 0; + + #if 0 + chessState.kings[Black].x = 4; + chessState.kings[Black].y = 6; + chessState.kings[White].x = 4; + chessState.kings[White].y = 0; + + chessState.kingMoved[0] = false; + chessState.kingMoved[1] = true; + chessState.kRookMoved[0] = false; + chessState.qRookMoved[0] = false; + chessState.kRookMoved[1] = true; + chessState.qRookMoved[1] = true; + + chessState.board[0][0] = Piece { White, Rook }; + chessState.board[0][4] = Piece { White, King }; + chessState.board[0][7] = Piece { White, Rook }; + chessState.board[1][0] = Piece { White, Pawn }; + chessState.board[1][2] = Piece { White, Pawn }; + chessState.board[1][5] = Piece { White, Pawn }; + chessState.board[1][6] = Piece { White, Pawn }; + chessState.board[1][7] = Piece { White, Pawn }; + chessState.board[2][1] = Piece { White, Pawn }; + chessState.board[2][2] = Piece { White, Knight }; + chessState.board[4][3] = Piece { White, Pawn }; + chessState.board[5][6] = Piece { White, Bishop }; + + chessState.board[5][0] = Piece { Black, Pawn }; + chessState.board[5][1] = Piece { Black, Pawn }; + chessState.board[5][3] = Piece { Black, Knight }; + chessState.board[5][5] = Piece { Black, Pawn }; + chessState.board[5][7] = Piece { Black, Bishop }; + chessState.board[6][0] = Piece { White, Rook }; + chessState.board[6][2] = Piece { Black, Pawn }; + chessState.board[6][3] = Piece { Black, Pawn }; + chessState.board[6][4] = Piece { Black, King }; + chessState.board[7][1] = Piece { Black, Knight }; + chessState.board[7][2] = Piece { Black, Bishop }; + #endif + chessState.turn = White; + + chessState.castled[White] = + chessState.castled[Black] = false; + + // EN PASSANT STATUS + chessState.enPassant.x = -1; + chessState.enPassant.y = -1; + + chessState.pieceCount[White][Pawn] = chessState.pieceCount[Black][Pawn] = 8; + chessState.pieceCount[White][Knight] = chessState.pieceCount[Black][Knight] = 2; + chessState.pieceCount[White][Bishop] = chessState.pieceCount[Black][Bishop] = 2; + chessState.pieceCount[White][Rook] = chessState.pieceCount[Black][Rook] = 2; + chessState.pieceCount[White][Queen] = chessState.pieceCount[Black][Queen] = 1; + chessState.pieceCount[White][King] = chessState.pieceCount[Black][King] = 1; + + chessState.materialValue[White] = + chessState.pieceCount[White][Pawn] * materialValues[Pawn] + + chessState.pieceCount[White][Knight] * materialValues[Knight] + + chessState.pieceCount[White][Bishop] * materialValues[Bishop] + + chessState.pieceCount[White][Rook] * materialValues[Rook] + + chessState.pieceCount[White][Queen] * materialValues[Queen]; + chessState.materialValue[Black] = + chessState.pieceCount[Black][Pawn] * materialValues[Pawn] + + chessState.pieceCount[Black][Knight] * materialValues[Knight] + + chessState.pieceCount[Black][Bishop] * materialValues[Bishop] + + chessState.pieceCount[Black][Rook] * materialValues[Rook] + + chessState.pieceCount[Black][Queen] * materialValues[Queen]; + + chess2D.Update(null); + chess3D.Update(null); + + if(chessState.isLocalPlayer[chessState.turn] && !local) + turnField.text = "Your turn"; + stateField.text = ""; + + /* + // 1 + MakeMoveChar('e',2, 'e',4); + MakeMoveChar('a',7, 'a',6); + // 2 + MakeMoveChar('d',2, 'd',4); + MakeMoveChar('f',7, 'f',5); + // 3 + MakeMoveChar('e',4, 'f',5); + MakeMoveChar('b',8, 'c',6); + // 4 + MakeMoveChar('d',4, 'd',5); + MakeMoveChar('c',6, 'e',5); + // 5 + MakeMoveChar('f',2, 'f',4); + MakeMoveChar('e',5, 'f',7); + // 6 + MakeMoveChar('g',1, 'f',3); + MakeMoveChar('c',7, 'c',5); + // 7 + MakeMoveChar('c',2, 'c',4); + MakeMoveChar('g',7, 'g',6); + // 8 + MakeMoveChar('f',5, 'g',6); + MakeMoveChar('f',7, 'd',6); + // 9 + MakeMoveChar('g',6, 'h',7); + MakeMoveChar('g',8, 'h',6); + // 10 + MakeMoveChar('f',1, 'd',3); + MakeMoveChar('d',6, 'f',7); + // 11 + MakeMoveChar('c',1, 'd',2); + MakeMoveChar('d',8, 'b',6); + // 12 + MakeMoveChar('d',2, 'c',3); + MakeMoveChar('b',6, 'd',6); + // 13 + MakeMoveChar('c',3, 'h',8); + MakeMoveChar('f',7, 'h',8); + // 14 + MakeMoveChar('d',1, 'd',2); + MakeMoveChar('e',7, 'e',6); + */ + } + + void MakeMoveChar(char x1, int y1, char x2, int y2) + { + MakeMove(x1 - 'a', y1 - 1, x2 - 'a', y2 - 1, Queen); + } + + void EnableMenus() + { + stopItem.disabled = !hosting; + disconnectItem.disabled = !sockets[SERVER_COLOR] && !sockets[CLIENT_COLOR]; + endGameItem.disabled = !chessState.gameRunning; + hostItem.disabled = hosting; + } + + void SetDriver() + { + int c; + for(c = 0; cgameRunning = false; + chess.turnField.text = ""; + chess.stateField.text = ""; + } + else if(this == chess.sockets[SERVER_COLOR]) + { + chess.sockets[SERVER_COLOR] = null; + chess.chessState->gameRunning = false; + chess.turnField.text = ""; + chess.stateField.text = ""; + } + + chess.EnableMenus(); + chess.chess2D.Update(null); + chess.chess3D.Update(null); + } + + uint OnReceive(const byte * buffer, uint count) + { + if(count >= sizeof(ChessPacket)) + { + ChessPacket packet = *(ChessPacket *)buffer; + switch(packet.type) + { + case Position: + chess.MakeMove(packet.x1, packet.y1, packet.x2, packet.y2, packet.promotion); + chess.Activate(); + break; + case NewGame: + chess.chessState->gameRunning = true; + chess.NewGame(); + break; + } + return sizeof(ChessPacket); + } + return 0; + } + + void OnConnect() + { + chess.sockets[SERVER_COLOR] = this; + chess.chessState->gameRunning = true; + chess.EnableMenus(); + chess.NewGame(); + } +} + +class ChessService : Service +{ + Chess chess; + property Chess chess { set { chess = value; } } + + void OnAccept() + { + if(!chess.chessState->gameRunning) + { + ChessPacket packet { type = NewGame }; + + chess.sockets[CLIENT_COLOR] = ChessSocket { this, chess = chess }; + chess.sockets[CLIENT_COLOR].Send((byte *)&packet, sizeof(ChessPacket)); + + chess.chessState->isLocalPlayer[SERVER_COLOR] = true; + chess.chessState->isLocalPlayer[CLIENT_COLOR] = false; + + chess.NewGame(); + chess.chessState->gameRunning = true; + chess.EnableMenus(); + chess.chess2D.Update(null); + chess.chess3D.Update(null); + } + } +} diff --git a/src/chess2D.ec b/src/chess2D.ec new file mode 100644 index 0000000..9f09b8c --- /dev/null +++ b/src/chess2D.ec @@ -0,0 +1,216 @@ +import "chess.ec" + +// #define FLIPBOARD + +/* +define OFFSET_X = 23; +define OFFSET_Y = 23; +define SQUARE_W = 49; +define SQUARE_H = 49; +*/ + +define OFFSET_X = 42; +define OFFSET_Y = 42; +define SQUARE_W = 90; +define SQUARE_H = 90; + +define BOARD_WIDTH = SQUARE_W * 8; +define BOARD_HEIGHT = SQUARE_H * 8; + +static char * names[12] = +{ + "whitePawn", "whiteKnight", "whiteBishop", "whiteRook", "whiteQueen", "whiteKing", + "blackPawn", "blackKnight", "blackBishop", "blackRook", "blackQueen", "blackKing" +}; + +class Chess2D : Window +{ + ChessState * chessState; + Point drag, moving; + bool dragging; + Bitmap piecesBMP[12]; + //BitmapResource boardBMP { ":board.pcx", window = this }; + BitmapResource boardBMP { ":board.jpg", window = this }; + + isActiveClient = true, background = black, borderStyle = fixed, hasMinimize = true; + + bool OnLoadGraphics() + { + int c; + +#if 0 + Bitmap chessPieces {}; + chessPieces.Load(":pieces.pcx", null, null); + for(c = 0; c<12; c++) + { + piecesBMP[c] = Bitmap {}; + piecesBMP[c].Allocate(null, SQUARE_W, SQUARE_H, 0, chessPieces.format, true); + piecesBMP[c].Grab(chessPieces, (c % 6) * SQUARE_H,(c / 6) * SQUARE_H); + piecesBMP[c].transparent = true; + piecesBMP[c].MakeDD(displaySystem); + } + delete chessPieces; +#endif + + for(c = 0; c<12; c++) + { + char fileName[MAX_FILENAME]; + sprintf(fileName, ":%s.png", names[c]); + piecesBMP[c] = Bitmap{}; + piecesBMP[c].LoadT(fileName, null, displaySystem); + } + return true; + } + + bool OnResizing(int * w, int * h) + { + *w = Max(*w, BOARD_WIDTH + OFFSET_X * 2); + *h = Max(*h, BOARD_HEIGHT + OFFSET_Y * 2); + return true; + } + + void OnUnloadGraphics() + { + int c; + for(c = 0; c<12; c++) + { + piecesBMP[c].Free(); + piecesBMP[c] = null; + } + } + + void OnRedraw(Surface surface) + { + bool flip; + int rx, ry; + + #ifdef FLIPBOARD + flip = chessState->turn == Black; + #else + flip = chessState->isLocalPlayer[Black] && !chessState->isLocalPlayer[White]; + #endif + + surface.SetForeground(white); + surface.Blit(boardBMP.bitmap, 0,0, 0,0, boardBMP.bitmap.width, boardBMP.bitmap.height); + + if(chessState->gameRunning) + { + int x,y; + for (y=0; y<8; y++) + { + for (x=0; x<8; x++) + { + if(!dragging || x != moving.x || y != moving.y) + { + Piece atBoard = chessState->board[y][x]; + int piece = (int)atBoard.player * 6 + (int)atBoard.type; + if(piece) + { + rx = flip ? (7-x) : x; + ry = flip ? y : (7-y); + + surface.Blit(piecesBMP[piece-1], + OFFSET_X + rx * SQUARE_W, OFFSET_Y + ry * SQUARE_H, + 0,0, piecesBMP[piece-1].width, piecesBMP[piece-1].height); + } + } + } + } + + if(dragging) + { + Piece atBoard = chessState->board[moving.y][moving.x]; + int piece = (int)atBoard.player * 6 + (int)atBoard.type; + + rx = flip ? (7-drag.x) : drag.x; + ry = flip ? drag.y : (7-drag.y); + + surface.Blit(piecesBMP[piece-1], + OFFSET_X + rx * SQUARE_W, OFFSET_Y + ry * SQUARE_H, + 0,0, piecesBMP[piece-1].width, piecesBMP[piece-1].height); + } + } + } + + bool OnLeftButtonDown(int x, int y, Modifiers mods) + { + bool flip; + #ifdef FLIPBOARD + flip = chessState->turn == Black; + #else + flip = chessState->isLocalPlayer[Black] && !chessState->isLocalPlayer[White]; + #endif + + x = (x - OFFSET_X) / SQUARE_W; + y = (y - OFFSET_Y) / SQUARE_H; + + if(!flip) y = 7-y; else x = 7-x; + + if(chessState->gameRunning && + x < 8 && y < 8 && x >= 0 && y >= 0 && chessState->board[y][x] && + chessState->isLocalPlayer[chessState->turn] && + chessState->board[y][x].player == chessState->turn) + { + dragging = true; + drag.x = moving.x = x; + drag.y = moving.y = y; + Capture(); + SetMouseRange( Box { OFFSET_X, OFFSET_Y, OFFSET_X + BOARD_WIDTH - 1, OFFSET_Y + BOARD_HEIGHT - 1 } ); + } + return true; + } + + bool OnLeftButtonUp(int x, int y, Modifiers mods) + { + bool flip; + + #ifdef FLIPBOARD + flip = chessState->turn == Black; + #else + flip = chessState->isLocalPlayer[Black] && !chessState->isLocalPlayer[White]; + #endif + + if(chessState->gameRunning) + { + x = (x - OFFSET_X) / SQUARE_W; + y = (y - OFFSET_Y) / SQUARE_H; + + if(!flip) y = 7-y; else x = 7-x; + + if(x < 8 && y < 8 && x >= 0 && y >= 0 && dragging) + { + ReleaseCapture(); + FreeMouseRange(); + dragging = false; + ((Chess)master).ProcessUserMove(moving.x, moving.y, x, y); + } + } + return true; + } + + bool OnMouseMove(int x, int y, Modifiers mods) + { + bool flip; + + #ifdef FLIPBOARD + flip = chessState->turn == Black; + #else + flip = chessState->isLocalPlayer[Black] && !chessState->isLocalPlayer[White]; + #endif + + x = (x - OFFSET_X) / SQUARE_W; + y = (y - OFFSET_Y) / SQUARE_H; + + if(!flip) y = 7-y; else x = 7-x; + + if(x < 8 && y < 8 && x >= 0 && y >= 0 && dragging) + { + drag.x = x; + drag.y = y; + Update(null); + } + return true; + } + + property ChessState * chessState { set { chessState = value; } } +} diff --git a/src/chess3D.ec b/src/chess3D.ec new file mode 100644 index 0000000..a09fd47 --- /dev/null +++ b/src/chess3D.ec @@ -0,0 +1,421 @@ +import "chess.ec" + +static char * names[Player][PieceType] = +{ + { "", "WhitePawn", "WhiteKnigh", "WhiteBisho", "WhiteRook", "WhiteQueen", "WhiteKing" }, + { "", "BlackPawn", "BlackKnigh", "BlackBisho", "BlackRook", "BlackQueen", "BlackKing" }, +}; + +define SQUARE = squareSize; // 160; +define SQUARE_OFFSET = SQUARE * 3.5f; + +class Chess3D : Window +{ + Camera camera + { + attached, fov = 45, zMin = 50,zMax = 5000, + position = { 0, 0, -1000 }, + orientation = Euler { 30, 30, 0 } + }; + + Object chessSet { }; + + Object chessBoard; + bool moving, lightMoving; + Point startPosition; + Euler startOrientation; + Light light; + FillModeValue fillMode; + ChessState * chessState; + double squareSize; + double offsetY; + + bool pieceSelected; + Point start; + + // Dragging + bool useDrag; + Point dragging; + double dragY; + + bool antiAlias; + + antiAlias = true; + + menu = Menu {}; + Menu viewMenu { menu, "View", v }; + MenuItem itemAntiAlias + { + viewMenu, "Anti Aliased", s, checkable = true, checked = antiAlias; + bool NotifySelect(MenuItem selection, Modifiers mods) + { + antiAlias ^= true; + Update(null); + return true; + } + }; + + isActiveClient = true, background = black, borderStyle = sizable, hasMaximize = true, hasMinimize = true; + + void RenderPiece(Piece atBoard, int x, int y, bool high) + { + Player player = atBoard.player; + PieceType type = atBoard.type; + + if(type) + { + char * name = names[player][type]; + Object object = chessSet.Find(name); + if(object) + { + + float height = 0; + if(high) + { + Piece overAtBoard = chessState->board[y][x]; + Player overPlayer = overAtBoard.player; + PieceType overType = overAtBoard.type; + if(overType) + { + char * name = names[overPlayer][overType]; + Object over = chessSet.Find(name); + if(over) + height = over.max.y - over.min.y; + } + } + + object.flags.root = true; + + object.transform.position = { + x * SQUARE - SQUARE_OFFSET, + offsetY - height; + y * SQUARE - SQUARE_OFFSET }; + + object.UpdateTransform(); + + object.tag = (void *)(((y)*8)+(x)+1); + display.DrawObject(object); + } + } + } + + void RenderSquare(int x, int y) + { + if(x >= 0 && x < 8 && y >= 0 && y < 8) + { + Piece atBoard = chessState->board[y][x]; + + if(useDrag && pieceSelected) + { + if(x == start.x && y == start.y) return; + if(x == dragging.x && y == dragging.y) + { + Piece atBoardStart = chessState->board[start.y][start.x]; + if(atBoard && atBoard.player != atBoardStart.player) return; + } + } + RenderPiece(atBoard, x, y, false); + } + } + + void RenderBoard() + { + display.DrawObject(chessBoard); + + if(chessState->gameRunning) + { + int x,y; + for(y=0; y<8; y++) + for(x=0; x<8; x++) + RenderSquare(x, y); + + if(useDrag && pieceSelected) + { + Piece atBoard = chessState->board[start.y][start.x]; + Piece atBoardDrag = chessState->board[dragging.y][dragging.x]; + RenderPiece(atBoard, dragging.x, dragging.y, + (dragging.x != start.x || dragging.y != start.y) && + atBoardDrag && atBoardDrag.player == atBoard.player); + } + } + } + + void OnUnloadGraphics() + { + displaySystem.ClearMaterials(); + displaySystem.ClearTextures(); + displaySystem.ClearMeshes(); + } + + void OnPosition(int x, int y, int w, int h) + { + camera.Setup(w, h, null); + } + + bool OnLeftButtonDown(int x, int y, Modifiers mods) + { + OldList list {}; + + display.StartSelection(x,y, 0,0); + display.SetCamera(null, camera); + display.CollectHits(); + + + if(chessState->gameRunning) + { + int cx,cy; + for(cy = 0; cy < 8; cy++) + for(cx = 0; cx < 8; cx++) + RenderSquare(cx, cy); + } + + if(display.GetHits(list)) + { + HitRecord hit = list.first; + int tag = ((int)hit.tags[0]) - 1; + int sx = tag & 7, sy = tag >> 3; + + if(pieceSelected) + { + pieceSelected = false; + ((Chess)master).ProcessUserMove(start.x, start.y, sx, sy); + } + else if(chessState->board[sy][sx] && + chessState->isLocalPlayer[chessState->turn] && + chessState->board[sy][sx].player == chessState->turn) + { + if(useDrag) + { + Vector3D viewSpace, worldSpace; + display.IntersectPolygons(); + RenderSquare(sx, sy); + display.GetIntersect(viewSpace); + + camera.Untransform(viewSpace, worldSpace); + + dragY = worldSpace.y; + dragging.x = sx; + dragging.y = sy; + } + Capture(); + pieceSelected = true; + start.x = sx; + start.y = sy; + Update(null); + + OnMouseMove(x, y, mods); + } + + list.Free(null); + } + else if(display.DrawObject(chessBoard) && !moving && !lightMoving) + { + startPosition.x = x; + startPosition.y = y; + startOrientation = camera.orientation; + Capture(); + moving = true; + } + display.SetCamera(null, null); + display.StopSelection(); + Update(null); + + return true; + } + + bool OnLeftButtonUp(int x, int y, Modifiers mods) + { + if(moving) + { + ReleaseCapture(); + moving = false; + } + if(useDrag && pieceSelected) + { + ReleaseCapture(); + ((Chess)master).ProcessUserMove(start.x, start.y, dragging.x, dragging.y); + Update(null); + pieceSelected = false; + } + return true; + } + + bool OnRightButtonDown(int x, int y, Modifiers mods) + { + if(!moving && !lightMoving) + { + startPosition.x = x; + startPosition.y = y; + startOrientation = light.orientation; + Capture(); + lightMoving = true; + } + return true; + } + + bool OnRightButtonUp(int x, int y, Modifiers mods) + { + if(lightMoving) + { + ReleaseCapture(); + lightMoving = false; + } + return true; + } + + bool OnMouseMove(int x, int y, Modifiers mods) + { + if(moving) + { + Euler angle + { + startOrientation.yaw - (x - startPosition.x), + startOrientation.pitch + (y - startPosition.y), + 0 + }; + if(angle.pitch > 90) angle.pitch = 90; + if(angle.pitch < 2) angle.pitch = 2; + camera.orientation = angle; + + Update(null); + } + else if(lightMoving) + { + light.orientation = Euler + { + startOrientation.yaw + (x - startPosition.x), + startOrientation.pitch + (y - startPosition.y), + 90 + }; + + Update(null); + } + else if(pieceSelected && useDrag) + { + Vector3D p, v1, v2, w1, w2, vector; + Line line; + Plane plane; + int sx, sy; + + // Compute ray of pixel + v1.x = 0; + v1.y = 0; + v1.z = 0; + p.x = (float)x; + p.y = (float)y; + p.z = 0; + camera.Unproject(p, v2); + + // Convert ray to world space + camera.Untransform(v1, w1); + camera.Untransform(v2, w2); + line.p0 = w1; + line.delta.x = w2.x - w1.x; + line.delta.y = w2.y - w1.y; + line.delta.z = w2.z - w1.z; + + // Compute plane of selection in world space + vector = { 0,dragY,0 }; + w1 = { 1,dragY,0 }; + w2 = { 0,dragY,1 }; + plane.FromPoints(vector, w1, w2); + + // Find intersection + plane.IntersectLine(line, vector); + + sx = (int)((vector.x + SQUARE * 4) / SQUARE); + sy = (int)((vector.z + SQUARE * 4) / SQUARE); + sx = Max(Min(sx, 7), 0); + sy = Max(Min(sy, 7), 0); + + dragging.x = sx; + dragging.y = sy; + Update(null); + } + return true; + } + + bool OnKeyHit(Key key, unichar ch) + { + switch((SmartKey)key) + { + case wheelDown: + case minus: + camera.position.z *= 1.1f; + if(camera.position.z <= -3800) + camera.position.z = -3800; + Update(null); + break; + case wheelUp: + case equal: camera.position.z /= 1.1f; + if(camera.position.z >= -1100) + camera.position.z = -1100; + Update(null); + break; + } + return true; + } + + bool OnLoadGraphics() + { + display.vSync = true; + chessSet.Load(":chessSet.3ds", null, displaySystem); + + //chessSet.transform.orientation.RotateY(45); + //chessSet.UpdateTransform(); + + chessBoard = chessSet.Find("Rectangle0"); + chessBoard.flags.root = true; + + { + Object queen = chessSet.Find(names[White][Queen]); + Object king = chessSet.Find(names[White][King]); + + squareSize = king.transform.position.x - queen.transform.position.x; + offsetY = king.transform.position.y; + } + + if(chessBoard) + camera.position.z = - chessBoard.radius * 2.5f; + + camera.target = chessBoard; + + return true; + } + + void OnRedraw(Surface surface) + { + //surface.SetBackground(white); + surface.Clear(colorAndDepth); + + camera.Update(); + display.antiAlias = antiAlias; + display.SetCamera(surface, camera); + //display.fogDensity = 0.0002f; + display.antiAlias = antiAlias; + + display.SetLight(0, light); + display.fillMode = fillMode; + + RenderBoard(); + + display.SetCamera(surface, null); + + display.fillMode = solid; + } + + bool OnCreate() + { + fillMode = solid; + //fillMode = wireframe; + + light.diffuse = white; + light.specular = white; + + light.orientation = Euler { pitch = 90 }; + useDrag = true; + + return true; + } + property ChessState * chessState { set { chessState = value; } } +} diff --git a/src/chessutils.ec b/src/chessutils.ec new file mode 100644 index 0000000..53ffb94 --- /dev/null +++ b/src/chessutils.ec @@ -0,0 +1,371 @@ +/**************************************************************************** + CHESS Game + + Copyright (c) 2001 Jerome Jacovella-St-Louis + All Rights Reserved. + + chessutils.ec - Utilities to validate moves +****************************************************************************/ +import "chess.ec" + +enum PieceType : byte { Empty, Pawn, Knight, Bishop, Rook, Queen, King }; +enum Player : byte { White, Black }; + +int materialValues[PieceType] = { 0, 10, 30, 35, 50, 90, 10000 }; + +class Piece : byte +{ +public: + Player player : 1 : 3; + PieceType type : 3 : 0; +}; + +struct ChessMove +{ + int x1, y1; + int x2, y2; + Player player; + PieceType type; + int rating; + PieceType promotion; + int count; +}; + +enum State { Normal, Check, CheckMate, StaleMate }; + +struct ChessState +{ + bool gameRunning; + bool isLocalPlayer[Player]; + + Point enPassant; + bool kingMoved[Player], qRookMoved[Player], kRookMoved[Player]; + Piece board[8][8]; + Player turn; + int numMoves; + Point kings[Player]; + int pieceCount[Player][PieceType]; + int materialValue[Player]; + bool castled[Player]; + + State state; +}; + +static bool FreeWay(Piece board[8][8], int x1, int y1, int x2, int y2) +{ + int x,y; + int dx = Sgn(x2-x1), dy = Sgn(y2-y1); + + for(x = x1+dx, y = y1+dy; x != x2 || y != y2; x+=dx, y+=dy) + if(board[y][x]) + return false; + return true; +} + +static bool BasicMove(Piece board[8][8], int x1, int y1, int x2, int y2) +{ + bool valid = false; + Piece source = board[y1][x1]; + Player player = source.player; + Piece dest = board[y2][x2]; + + if(!dest || player != dest.player) + { + PieceType piece = source.type; + int dx = x2 - x1; + int dy = y2 - y1; + + switch(piece) + { + case Pawn: + { + int direction = (player == White) ? 1 : -1; + if(dy == direction && Abs(dx) == 1 && dest) + valid = true; + break; + } + case Knight: + if( (Abs(dx) == 1 && Abs(dy) == 2) || + (Abs(dy) == 1 && Abs(dx) == 2)) + valid = true; + break; + case Bishop: + if(Abs(dx) == Abs(dy) && FreeWay(board, x1,y1,x2,y2)) + valid = true; + break; + case Rook: + if((!Abs(dx) || !Abs(dy)) && FreeWay(board, x1,y1,x2,y2)) + valid = true; + break; + case Queen: + if((!Abs(dx) || !Abs(dy) || Abs(dx) == Abs(dy)) && + FreeWay(board, x1,y1,x2,y2)) + valid = true; + break; + case King: + if(Abs(dx) <= 1 && Abs(dy) <= 1) + valid = true; + break; + } + } + return valid; +} + +bool Check(ChessState state, Player player, int kx, int ky) +{ + int x,y; + Piece old1 = 0xFF, old2; + bool checked = false; + + if(kx == -1) + { + kx = state.kings[player].x; + ky = state.kings[player].y; + } + else + { + old1 = state.board[state.kings[player].y][state.kings[player].x]; + state.board[state.kings[player].y][state.kings[player].x] = Piece { }; + + old2 = state.board[ky][kx]; + state.board[ky][kx] = Piece { player, King }; + } + + for(y = 0; y<8; y++) + { + for(x = 0; x<8; x++) + { + Piece atBoard = state.board[y][x]; + if(atBoard && atBoard.player != player) + { + if(BasicMove(state.board, x, y, kx, ky)) + { + checked = true; + break; + } + } + } + if(checked) + break; + } + + if(old1 != 0xFF) + { + state.board[ky][kx] = old2; + state.board[state.kings[player].y][state.kings[player].x] = old1; + } + return checked; +} + +bool IsMoveValid(int x1, int y1, int x2, int y2, ChessState state, Piece endBoard[8][8], bool validate) +{ + bool valid = false; + Piece source = state.board[y1][x1]; + PieceType piece = source.type; + Player player = source.player; + + int dx = x2 - x1; + int dy = y2 - y1; + + if(!validate || (x2 >= 0 && y2 >= 0 && x2<8 && y2<8)) + { + if(!(valid = (validate ? BasicMove(state.board, x1,y1,x2,y2) : true))) + { + // Handle special moves (pawn & castle) + switch(piece) + { + case Pawn: + { + int direction = (player == White) ? 1 : -1; + int start = (player == White) ? 1 : 6; + if(dy == direction) + { + // Normal Move + if(x1 == x2 && !state.board[y2][x2]) + valid = true; + // En Passant + else if(Abs(dx) == 1 && + x2 == state.enPassant.x && y2 == state.enPassant.y + direction) + valid = true; + } + // First 2 Squares Move + else if(y2 - y1 == direction * 2 && y1 == start && x1 == x2 + && !state.board[y1+direction][x1] && !state.board[y2][x1]) + valid = true; + break; + } + case King: + // Castle + if(!dy && Abs(dx) == 2) + { + if(!state.kingMoved[player] && !Check(state, player, x1, y1)) + { + // King Side + if(dx == 2 && !state.kRookMoved[player] && + !state.board[y1][5] && !state.board[y1][6] && + !Check(state, player, 5, y1)) + valid = true; + // Queen Side + else if(dx == -2 && !state.qRookMoved[player] && + !state.board[y1][3] && !state.board[y1][2] && !state.board[y1][1] && + !Check(state, player, 3, y1)) + valid = true; + } + } + break; + } + } + } + + if(valid) + { + if(validate || endBoard) + { + ChessState tmpState = state; + tmpState = state; + tmpState.board[y1][x1] = Piece {}; + + // Handle special moves (pawn & castle) + switch(piece) + { + case Pawn: + { + int direction = (player == White) ? 1 : -1; + int start = (player == White) ? 1 : 6; + if(dy == direction) + { + // Normal Move + if(Abs(dx) == 1 && x2 == state.enPassant.x && y2 == state.enPassant.y + direction) + tmpState.board[state.enPassant.y][state.enPassant.x] = Piece {}; + } + break; + } + case King: + // *** Castle *** + + // King Side + if(dx == 2) + { + tmpState.board[y1][7] = Piece { }; + tmpState.board[y1][5] = Piece { player, Rook }; + } + // Queen Side + else if(dx == -2) + { + tmpState.board[y1][0] = Piece { }; + tmpState.board[y1][3] = Piece { player, Rook }; + } + + // Keep Track of King + tmpState.kings[player].x = x2; + tmpState.kings[player].y = y2; + break; + } + + tmpState.board[y2][x2] = state.board[y1][x1]; + + valid = validate ? !Check(tmpState, player, -1, -1) : true; + if(valid && endBoard) + CopyBytes(endBoard, tmpState.board, 64); + } + } + return valid; +} + +bool StateMakeMove(ChessState state, int x1, int y1, int x2, int y2, PieceType promotion, bool validate, int * delta) +{ + bool valid = false; + PieceType type = state.board[y1][x1].type; + Player player = state.board[y1][x1].player; + + PieceType captured = state.board[y2][x2].type; + if(captured) + { + if(x2 == 7 && y2 == ((player == White) ? 7 : 0) && !state.kRookMoved[(Player)!player]) + state.kRookMoved[(Player)!player] = true; + else if(x2 == 0 && y2 == ((player == White) ? 7 : 0) && !state.qRookMoved[(Player)!player]) + state.qRookMoved[(Player)!player] = true; + + state.pieceCount[(Player)!player][captured]--; + state.materialValue[(Player)!player] -= materialValues[captured]; + if(delta) + *delta = materialValues[captured]; + } + // En Passant + else if(type == Pawn && Abs(x2-x1) == 1) + { + state.pieceCount[(Player)!player][Pawn]--; + state.materialValue[(Player)!player] -= materialValues[Pawn]; + if(delta) + *delta = materialValues[Pawn]; + } + + if(IsMoveValid(x1,y1,x2,y2, state, state.board, validate)) + { + valid = true; + + // En Passant + if(type == Pawn && Abs(y2-y1) == 2) + { + state.enPassant.x = x2; + state.enPassant.y = y2; + } + else + { + state.enPassant.x = -1; + state.enPassant.y = -1; + } + + // Castle + if(type == King) + { + switch(x2-x1) + { + // King Side Castle + case 2: + state.kRookMoved[player] = true; + state.castled[player] = true; + break; + // Queen Side Castle + case -2: + state.qRookMoved[player] = true; + state.castled[player] = true; + break; + } + } + + // Rook moved (can't castle with it) + if(type == Rook && y1 == ((player == White) ? 0 : 7)) + { + if(!state.qRookMoved[player] && x1 == 0) + state.qRookMoved[player] = true; + else if(!state.kRookMoved[player] && x1 == 7) + state.kRookMoved[player] = true; + } + + // Pawn Promotion + if(type == Pawn && y2 == ((player == White) ? 7 : 0)) + { + state.board[y2][x2] = Piece { player, promotion }; + + state.pieceCount[player][Pawn]--; + state.pieceCount[player][promotion]++; + state.materialValue[player] += materialValues[promotion] - materialValues[Pawn]; + if(delta) + *delta += materialValues[promotion] - materialValues[Pawn]; + } + + // Keep track of the kings + if(type == King) + { + state.kings[player].x = x2; + state.kings[player].y = y2; + + state.kingMoved[player] = true; + } + + state.turn ^= 1; + state.numMoves ++; + } + return valid; +} diff --git a/src/connect.ec b/src/connect.ec new file mode 100644 index 0000000..d37095e --- /dev/null +++ b/src/connect.ec @@ -0,0 +1,38 @@ +import "chess.ec" + +class ConnectDialog : Window +{ + minClientSize = Size { 300, 100 }; + tabCycle = true, background = activeBorder, hasClose = true, text = "Connect to server"; + + Button ok + { + parent = this, bevel = true, isDefault = true, text = "OK", + size = Size { w = 80 }, anchor = Anchor { horz = -48, bottom = 10 }; + + bool NotifyClicked(Button button, int x, int y, Modifiers mods) + { + ((Chess)master).Connect(address.line.text); + Destroy(0); + return true; + } + }; + + Button cancel + { + parent = this, bevel = true, text = "Cancel", size = Size { w = 80 }, hotKey = escape; + anchor = Anchor { horz = 48, bottom = 10 }; + + bool NotifyClicked(Button button, int x, int y, Modifiers mods) + { + Destroy(0); + return false; + } + }; + + EditBox address + { + parent = this, textHorzScroll = true, size = Size { w = 200 }, anchor = Anchor { top = 10 }, + line.text = "localhost" + }; +} diff --git a/src/promotion.ec b/src/promotion.ec new file mode 100644 index 0000000..cbb2a19 --- /dev/null +++ b/src/promotion.ec @@ -0,0 +1,43 @@ +/**************************************************************************** + CHESS Game + + Copyright (c) 2001 Jerome Jacovella-St-Louis + All Rights Reserved. + + promotion.c - Pawn Promotion Window +****************************************************************************/ +import "chessutils.ec" + +class Promotion : Window +{ + background = gray, text = "Pawn Promotion", borderStyle = fixed, tabCycle = true, + minClientSize = Size { 120, 140 }; + + bool ButtonClicked(Button button, int x, int y, Modifiers mods) + { + Destroy(button.id); + return true; + } + + Button + { + parent = this, bevel = true, text = "Knight", position = Point { 20, 10 }, + size = Size { 80, 20 }, id = PieceType::Knight, NotifyClicked = ButtonClicked + }; + Button + { + parent = this, bevel = true, text = "Bishop", position = Point { 20, 35 }, + size = Size { 80, 20 }, id = PieceType::Bishop, NotifyClicked = ButtonClicked + }; + Button + { + parent = this, bevel = true, text = "Rook", position = Point { 20, 60 }, + size = Size { 80, 20 }, id = PieceType::Rook, NotifyClicked = ButtonClicked + }; + Button + { + parent = this, bevel = true, text = "Queen", position = Point { 20, 85 }, + size = Size { 80, 20 }, id = PieceType::Queen, NotifyClicked = ButtonClicked, + isDefault = true + }; +}