Que bien! Antonio, esperamos oír y ver todo esto el sábado en el Webinar. Gracias por todo.
Carlos
Que bien! Antonio, esperamos oír y ver todo esto el sábado en el Webinar. Gracias por todo.
Carlos
Antonio Linares wrote:https://ai.fivetechsoft.com/facturas_mysql.prg
100% coded with HIX + Antigravity
Full mysql support :wink: :!:
Estimado Antonio
Puede publicar los fuentes de éste ejemplo porfa para probar
También el con DBFs
Muchas gracias
Estimado Adhemar,
Vamos a publicar todo el código fuente, lo entregaremos mañana durante el webinar.
Para usar la versión de mysql hace falta una actualización de HIX :idea:
Perfecto.
Espero para mañana
Gracias.
New game hix chess, changes implemented following Antonio's example

Implemented features
there are many files , more function and images are of my Tchess.prg class made on November, I convert them on Hix
I cannot all them insert here.
chess.prg
// Chess Game Logic for HIX
// Uses FEN for state management
function Main()
local oErr, cHtml
EnsureHistoryDBF()
BEGIN SEQUENCE
cHtml := ChessGame()
RECOVER USING oErr
cHtml := "<h2>Error: " + ValToChar(oErr:Description) + "</h2>"
cHtml += "<p>Operation: " + ValToChar(oErr:Operation) + "</p>"
cHtml += "<p>Filename: " + ValToChar(oErr:Filename) + "</p>"
cHtml += "<p>GenCode: " + ValToChar(oErr:GenCode) + "</p>"
cHtml += "<p>SubCode: " + ValToChar(oErr:SubCode) + "</p>"
cHtml += "<p>Args: " + ValToChar(oErr:Args) + "</p>"
END
return cHtml
function ChessGame()
local hPost := UPost()
local cHtml := ""
local cAction := "new", hParams
local cFEN := "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
local cGameID := "", cMoveStr, cSelected, cTurn, lSaveMove := .F.
local aValidMoves := {}, cClicked, cPiece, cTurnColor, lInCheck, lIsMate
local hConfig
local cGameMode := "HvH" // HvH or HvPC
local cPlayer1, cPlayer2, nLevel
// local cPlayer1 := "Giocatore 1" // Init from Config below
// local cPlayer2 := "Giocatore 2"
// local nLevel := 1
local aLevels := { "Neofita", "Principiante", "Intermedio", "Intermedio II", "Avanzato", "Esperto", "Maestro", "Grande Maestro", "Massimo" }
local cMyColor := "White"
local hLang := {=>} // Language hash
EnsureHistoryDBF()
EnsureSavedGamesDBF()
hConfig := LoadSettings()
// Load Language
hLang := LoadLanguage( hConfig["Language"] )
// Init Defaults from Config
cPlayer1 := iif(hb_HHasKey(hConfig,"Player1"), hConfig["Player1"], T("DEFAULT_PLAYER1", hLang))
cPlayer2 := iif(hb_HHasKey(hConfig,"Player2"), hConfig["Player2"], T("DEFAULT_PLAYER2", hLang))
nLevel := iif(hb_HHasKey(hConfig,"Level"), Val(hConfig["Level"]), 1)
if hb_HHasKey( hPost, "action" )
cAction := hPost[ "action" ]
endif
// Read persisted state from Hidden Inputs (if strictly reloading/moving)
if hb_HHasKey( hPost, "player1" ); cPlayer1 := hPost["player1"]; endif
if hb_HHasKey( hPost, "player2" ); cPlayer2 := hPost["player2"]; endif
if hb_HHasKey( hPost, "game_mode" ); cGameMode := hPost["game_mode"]; endif
if hb_HHasKey( hPost, "level" ); nLevel := Val(hPost["level"]); endif
if hb_HHasKey( hPost, "my_color" ); cMyColor := hPost["my_color"]; endif
// Handle New Game start
if cAction == "new_game_start"
cGameID := GetNewGameID()
cFEN := "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
cPlayer1 := iif( hb_HHasKey(hPost, "player1"), hPost["player1"], "Giocatore 1" )
cPlayer2 := iif( hb_HHasKey(hPost, "player2"), hPost["player2"], "Giocatore 2" )
cGameMode := iif( hb_HHasKey(hPost, "game_mode"), hPost["game_mode"], "HvH" )
if hb_HHasKey( hPost, "level" ); nLevel := Val(hPost["level"]); endif
cMyColor := iif( hb_HHasKey(hPost, "my_color"), hPost["my_color"], "White" )
endif
if cAction == "new" .or. (cAction == "new_game_start" .and. Empty(cGameID))
cGameID := GetNewGameID()
endif
if hb_HHasKey( hPost, "fen" )
cFEN := hPost["fen"]
else
cFEN := InitBoard()
endif
if hb_HHasKey( hPost, "game_id" )
cGameID := hPost["game_id"]
endif
if Empty( cFEN )
cFEN := InitBoard()
endif
if Empty( cGameID )
cGameID := GetNewGameID()
endif
cSelected := ""
if hb_HHasKey( hPost, "selected" )
cSelected := hPost["selected"]
endif
do case
case cAction == "new"
cFEN := InitBoard()
cGameID := GetNewGameID()
cSelected := ""
case cAction == "new_game_start"
cFEN := InitBoard()
cGameID := GetNewGameID()
cSelected := ""
// Update vars from Post
if hb_HHasKey(hPost, "player1"); cPlayer1 := hPost["player1"]; endif
if hb_HHasKey(hPost, "player2"); cPlayer2 := hPost["player2"]; endif
if hb_HHasKey(hPost, "level"); nLevel := Val(hPost["level"]); endif
// Persist to Config
hConfig["Player1"] := cPlayer1
hConfig["Player2"] := cPlayer2
hConfig["Level"] := AllTrim(Str(nLevel))
SaveSettings( hConfig )
case cAction == "select"
cClicked := hPost["clicked_square"]
if !Empty( cClicked )
cPiece := GetPieceAt( cFEN, cClicked )
cTurnColor := GetTurn( cFEN )
if cClicked == cSelected
cSelected := "" // Deselect
else
if !Empty(cSelected)
// Try to move
if IsValidMove( cFEN, cSelected, cClicked )
cMoveStr := GetSAN( cFEN, cSelected, cClicked )
cFEN := MakeMove( cFEN, cSelected, cClicked )
lSaveMove := .T.
cSelected := "" // Move done, deselect
else
// Invalid move.
// If clicked square has own piece, switch selection.
if IsOwnPiece( cPiece, cTurnColor )
cSelected := cClicked
aValidMoves := GetLegalMoves( cFEN, cSelected )
else
// Invalid move to empty or enemy square, effectively deselect/reset
cSelected := ""
endif
endif
else
// Nothing selected, selecting now
if IsOwnPiece( cPiece, cTurnColor )
cSelected := cClicked
aValidMoves := GetLegalMoves( cFEN, cSelected )
endif
endif
endif
endif
case cAction == "computer_move"
// AI Turn
// Ensure it is computer's turn
cTurnColor := GetTurn( cFEN )
if ( cGameMode == "HvPC" ) .and. ( cTurnColor != Left(hPost["my_color"],1) )
// Calculate Move
nLevel := Val(hPost["level"])
cMoveStr := GetComputerMove( cFEN, nLevel, cTurnColor )
if !Empty( cMoveStr )
// Parse From/To from string (e.g. "e2e4")
cSelected := Left( cMoveStr, 2 )
cClicked := Right( cMoveStr, 2 )
cFEN := MakeMove( cFEN, cSelected, cClicked )
lSaveMove := .T.
endif
endif
cSelected := ""
case cAction == "save_game"
if hb_HHasKey(hPost, "save_name")
cGameID := GetNewGameID() // Or use current? Better to generate new ID for saved state or keep current.
// Actually, if we save, we might want to keep the same ID or not.
// Let's just save the current snapshot.
if UseSavedGames()
Append Blank
SAVED->GAMEID := iif(Empty(cGameID), GetNewGameID(), cGameID)
SAVED->NAME := hPost["save_name"]
SAVED->FEN := cFEN
SAVED->DATE := Date()
SAVED->TIME := Time()
SAVED->MODE := cGameMode
SAVED->P1NAME := cPlayer1
SAVED->P2NAME := cPlayer2
Close SAVED
hParams["status_msg"] := "Partita Salvata: " + hPost["save_name"]
endif
endif
case cAction == "load_game_list"
cHtml := ""
if UseSavedGames()
Go Bottom
do while !Bof()
// list item
cName := AllTrim(SAVED->NAME)
cDate := DtoC(SAVED->DATE)
cID := AllTrim(SAVED->GAMEID)
cHtml += '<li style="margin-bottom:5px;">' + ;
'<button type="button" class="btn" style="width:100%; text-align:left;" ' + ;
"onclick='loadGame(" + '"' + cID + '"' + ")'>" + ;
'<b>' + cName + '</b> <span style="font-size:12px; float:right;">' + cDate + '</span>' + ;
'</button></li>'
Skip -1
enddo
Close SAVED
endif
hParams["saved_games_list"] := cHtml
hParams["open_load_modal"] := "Y"
case cAction == "load_game" // triggered by hidden input logic
// We need a separate JS function `loadGame(id)` to set action='load_game' and game_id input
if hb_HHasKey(hPost, "game_id")
if UseSavedGames()
Locate for AllTrim(GAMEID) == AllTrim(hPost["game_id"])
if Found()
cFEN := AllTrim(SAVED->FEN)
cGameID := AllTrim(SAVED->GAMEID)
cGameMode := AllTrim(SAVED->MODE)
cPlayer1 := AllTrim(SAVED->P1NAME)
cPlayer2 := AllTrim(SAVED->P2NAME)
hParams["status_msg"] := "Partita Caricata: " + AllTrim(SAVED->NAME)
endif
Close SAVED
endif
endif
case cAction == "save_settings"
hConfig["SquareWhite"] := hPost["square_white"]
hConfig["SquareBlack"] := hPost["square_black"]
hConfig["Background"] := hPost["background"]
hConfig["ShowCoords"] := iif( hb_HHasKey(hPost,"show_coords"), .T., .F. )
hConfig["CoordsMode"] := hPost["coords_mode"] // "Normal" or "Full"
hConfig["Theme"] := hPost["theme"]
hConfig["TextColor"] := hPost["text_color"]
if hb_HHasKey(hPost, "language")
hConfig["Language"] := hPost["language"]
// Reload language for immediate effect
hLang := LoadLanguage( hConfig["Language"] )
endif
SaveSettings( hConfig )
// Stay on current FEN/Game
endcase
if lSaveMove
SaveMoveToDBF( cGameID, cMoveStr, cFEN )
endif
// Post-move/action status check
cTurnColor := GetTurn(cFEN)
lInCheck := IsCheck( cFEN, cTurnColor )
lIsMate := .F.
if lInCheck
lIsMate := IsCheckmate( cFEN, cTurnColor )
endif
hParams := {;
"fen" => cFEN, ;
"board_html" => RenderBoard( cFEN, cSelected, aValidMoves, lInCheck, hConfig, (cMyColor == "Black") ), ;
"turn" => iif( cTurnColor == "w", T("WHITE", hLang), T("BLACK", hLang) ), ;
"status_msg" => iif( lIsMate, T("CHECKMATE", hLang) + " " + iif(cTurnColor=="w",T("BLACK", hLang),T("WHITE", hLang)) + " " + T("WINS", hLang), iif(lInCheck, T("CHECK", hLang), "") ), ;
"selected" => cSelected, ;
"game_id" => cGameID, ;
"moves_list" => GetMovesHTML( cGameID ), ;
"player1" => cPlayer1, ;
"player2" => cPlayer2, ;
"game_mode" => cGameMode, ;
"level" => AllTrim(Str(nLevel)), ;
"my_color" => cMyColor, ;
"computer_thinking" => iif( (cGameMode == "HvPC" .and. cTurnColor != Left(cMyColor,1) .and. !lIsMate), "Y", "N" ), ;
"cfg_white" => hConfig["SquareWhite"], ;
"cfg_black" => hConfig["SquareBlack"], ;
"cfg_bg" => hConfig["Background"], ;
"cfg_coord_chk" => iif(hConfig["ShowCoords"], "checked", ""), ;
"chk_normal" => iif(hConfig["CoordsMode"] == "Normal", "checked", ""), ;
"chk_full" => iif(hConfig["CoordsMode"] == "Full", "checked", ""), ;
"cfg_theme" => hConfig["Theme"], ;
"cfg_text" => hConfig["TextColor"], ;
"theme_options" => GetThemesOptions( hConfig["Theme"] ), ;
"language_options" => GetLanguageOptions( hConfig["Language"] ), ;
"Player1" => cPlayer1, ;
"Player2" => cPlayer2, ;
"has_active_action" => iif( !Empty(cAction) .and. cAction != "new", "Y", "N" ), ;
"splash_style" => iif( !Empty(cAction) .and. cAction != "new", "none !important", "flex" ), ;
"L_NEW_GAME" => T("NEW_GAME", hLang), ;
"L_LOAD_GAME" => T("LOAD_GAME", hLang), ;
"L_SAVE_GAME" => T("SAVE_GAME", hLang), ;
"L_EXIT" => T("EXIT", hLang), ;
"L_HISTORY" => T("HISTORY", hLang), ;
"L_PIECES" => T("PIECES", hLang), ;
"L_BOARD" => T("BOARD", hLang), ;
"L_BOARD_DEFAULT" => T("BOARD_DEFAULT", hLang), ;
"L_BOARD_WOOD" => T("BOARD_WOOD", hLang), ;
"L_SHOW_COORDS" => T("SHOW_COORDS", hLang), ;
"L_COORDS_OUTSIDE" => T("COORDS_OUTSIDE", hLang), ;
"L_COORDS_FULL" => T("COORDS_FULL", hLang), ;
"L_PIECE_NOTATION" => T("PIECE_NOTATION", hLang), ;
"L_ICONS" => T("ICONS", hLang), ;
"L_TEXT_COLOR" => T("TEXT_COLOR", hLang), ;
"L_LANGUAGE" => "Language", ;
"L_CANCEL" => T("CANCEL", hLang), ;
"L_SAVE" => T("SAVE", hLang), ;
"L_NEW_GAME_TITLE" => T("NEW_GAME_TITLE", hLang), ;
"L_GAME_TYPE" => T("GAME_TYPE", hLang), ;
"L_HUMAN_VS_HUMAN" => T("HUMAN_VS_HUMAN", hLang), ;
"L_HUMAN_VS_PC" => T("HUMAN_VS_PC", hLang), ;
"L_PLAYER1" => T("PLAYER1", hLang), ;
"L_PLAYER2" => T("PLAYER2", hLang), ;
"L_AI_LEVEL" => T("AI_LEVEL", hLang), ;
"L_YOUR_COLOR" => T("YOUR_COLOR", hLang), ;
"L_START" => T("START", hLang), ;
"L_LEVEL_1" => T("LEVEL_1", hLang), ;
"L_LEVEL_2" => T("LEVEL_2", hLang), ;
"L_LEVEL_3" => T("LEVEL_3", hLang), ;
"L_LEVEL_4" => T("LEVEL_4", hLang), ;
"L_LEVEL_5" => T("LEVEL_5", hLang), ;
"L_LEVEL_6" => T("LEVEL_6", hLang), ;
"L_LEVEL_7" => T("LEVEL_7", hLang), ;
"L_LEVEL_8" => T("LEVEL_8", hLang), ;
"L_LEVEL_9" => T("LEVEL_9", hLang), ;
"L_SAVE_GAME_TITLE" => T("SAVE_GAME_TITLE", hLang), ;
"L_GAME_NAME" => T("GAME_NAME", hLang), ;
"L_ENTER_NAME" => T("ENTER_NAME", hLang), ;
"L_LOAD_GAME_TITLE" => T("LOAD_GAME_TITLE", hLang), ;
"L_CLOSE" => T("CLOSE", hLang), ;
"L_COMPUTER_THINKING" => T("COMPUTER_THINKING", hLang), ;
"L_ENTER_NAME_ALERT" => T("ENTER_NAME_ALERT", hLang) ;
}
cHtml := View( "chess", hParams )
// Removed <h2> injection. Status now handled by Modal via hParams["status_msg"]
return cHtml
//----------------------------------------------------------------------------//
function InitBoard()
// Standard starting position FEN
return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
function GetTurn( cFEN )
local aParts := hb_ATokens( cFEN, " " )
if Len(aParts) >= 2
return aParts[2]
endif
return "w"
function GetPieceAt( cFEN, cSquare )
local aGrid := FENToGrid( cFEN )
local aCoords := SquareToCoords( cSquare )
return aGrid[ aCoords[1], aCoords[2] ]
function IsOwnPiece( cPiece, cTurnColor )
if Empty(cPiece); return .F.; endif
if cTurnColor == "w"
return IsUpperLocal( cPiece ) // White pieces are uppercase
else
return !IsUpperLocal( cPiece ) // Black pieces are lowercase
endif
return .F.
function GetSAN( cFEN, cFrom, cTo )
local cSAN := ""
local cPiece := GetPieceAt( cFEN, cFrom )
local cTarget := GetPieceAt( cFEN, cTo )
local lCapture := !Empty( cTarget )
local cType := Upper(cPiece)
local cTargetCol := Left(cTo, 1) // e.g., 'e' from 'e4'
local cSourceCol := Left(cFrom, 1) // e.g., 'd' from 'd4'
local cNextFEN, lCheck, lMate, cTurn, cOppColor
// It's technically possible for En Passant to be a capture where cTarget is empty but the move is diagonal pawn.
// Simplified: IsOwnPieces check handled by valid move logic.
// But to correctly mark 'x', check if diagonal pawn move to empty square (En Passant) or standard capture.
if cType == "P" .and. cSourceCol != cTargetCol .and. Empty(cTarget)
lCapture := .T. // En Passant
endif
// Piece Notation (Italian)
do case
case cType == "P"
cSAN := ""
case cType == "K"; cSAN := "R"
case cType == "Q"; cSAN := "D"
case cType == "R"; cSAN := "T"
case cType == "B"; cSAN := "A"
case cType == "N"; cSAN := "C"
endcase
// Captures
if lCapture
if cType == "P"
cSAN += cSourceCol
endif
cSAN += "x"
endif
cSAN += cTo
// Check/Mate detection (Simulate move)
cNextFEN := MakeMove( cFEN, cFrom, cTo, .T. )
cTurn := GetTurn( cFEN ) // Who moved?
// MakeMove toggles turn, so cNextFEN has opponent to move.
// We want to see if Opponent is in check.
// Opponent Color:
cOppColor := iif( cTurn == "w", "b", "w" )
lCheck := IsCheck( cNextFEN, cOppColor )
if lCheck
if IsCheckmate( cNextFEN, cOppColor )
cSAN += "#"
else
cSAN += "+"
endif
endif
// TODO: Castling (0-0, 0-0-0) logic would go here updating cSAN
// Detecting castling from from/to squares:
if cType == "K" .and. Abs( Asc(Left(cFrom,1)) - Asc(Left(cTo,1)) ) == 2
if cTo > cFrom // Kingside? (e.g. e1 -> g1)
cSAN := "0-0"
else
cSAN := "0-0-0"
endif
endif
return cSAN
//----------------------------------------------------------------------------//
// Persistence
//----------------------------------------------------------------------------//
// Persistence
function EnsureHistoryDBF()
local aStruct := {;
{ "GAMEID", "C", 14, 0 }, ;
{ "SEQ", "N", 3, 0 }, ;
{ "MOVE", "C", 10, 0 }, ;
{ "FEN", "C", 90, 0 }, ;
{ "TS", "C", 14, 0 } ;
}
local cPath := "c:\hix\mosse\"
if !IsDirectory( cPath )
DirMake( cPath )
endif
if !File( cPath + "moves.dbf" )
dbCreate( cPath + "moves.dbf", aStruct )
endif
if !File( cPath + "moves_gid.cdx" )
use ( cPath + "moves" ) exclusive new
index on GAMEID tag GAMEID
use
endif
return nil
function EnsureSavedGamesDBF()
local cPath := "c:\hix\mosse\"
if !IsDirectory( cPath )
DirMake( cPath )
endif
if !File( cPath + "saved.dbf" )
DbCreate( cPath + "saved", {;
{ "GAMEID", "C", 20, 0 },;
{ "NAME", "C", 40, 0 },;
{ "FEN", "C", 200, 0 },;
{ "DATE", "D", 8, 0 },;
{ "TIME", "C", 8, 0 },;
{ "MODE", "C", 10, 0 },; // HvH or HvPC
{ "P1NAME", "C", 30, 0 },;
{ "P2NAME", "C", 30, 0 } ;
})
endif
// No complex index needed for now, maybe by Date
return nil
function GetNewGameID()
return DtoS(Date()) + StrTran(Time(), ":", "")
function SaveMoveToDBF( cGameID, cMove, cFEN )
local nSeq := 1
USE "c:\hix\mosse\moves.dbf" SHARED NEW ALIAS "MOVES"
SET INDEX TO "c:\hix\mosse\moves_gid"
COUNT TO nSeq FOR FIELD->GAMEID == cGameID
nSeq++
APPEND BLANK
FIELD->GAMEID := cGameID
FIELD->SEQ := nSeq
FIELD->MOVE := cMove
FIELD->FEN := cFEN
FIELD->TS := DtoS(Date()) + StrTran(Time(), ":", "")
USE
return nil
function GetMovesHTML( cGameID )
local cHtml := "<ul>"
local cClass
USE "c:\hix\mosse\moves.dbf" SHARED NEW ALIAS "MOVES"
SET FILTER TO FIELD->GAMEID == cGameID
GO TOP
while !Eof()
cHtml += "<li>" + AllTrim(Str(FIELD->SEQ)) + ". " + FIELD->MOVE + "</li>"
SKIP
end
USE
cHtml += "</ul>"
return cHtml
//----------------------------------------------------------------------------//
function IsCheckmate( cFEN, cColor )
local aGrid := FENToGrid( cFEN )
local r, c, cPiece
local aMoves
// Check every piece of current color
for r := 1 to 8
for c := 1 to 8
cPiece := aGrid[r,c]
if !Empty(cPiece)
if (cColor == "w" .and. IsUpperLocal(cPiece)) .or. (cColor == "b" .and. !IsUpperLocal(cPiece))
// It's my piece. Can I move it legally?
aMoves := GetLegalMoves( cFEN, CoordsToSquare(r,c) )
if Len(aMoves) > 0
return .F. // Found at least one legal move
endif
endif
endif
next
next
return .T. // No legal moves found
function IsValidMove( cFEN, cFrom, cTo )
local aMoves := GetLegalMoves( cFEN, cFrom )
local i
for i := 1 to Len(aMoves)
if aMoves[i] == cTo
return .T.
endif
next
return .F.
function GetLegalMoves( cFEN, cFrom )
local aPseudoMoves := GetPseudoValidMoves( cFEN, cFrom )
local aLegalMoves := {}
local i, cNextFEN, cTurn
cTurn := GetTurn( cFEN )
for i := 1 to Len(aPseudoMoves)
// Simulate move
cNextFEN := MakeMove( cFEN, cFrom, aPseudoMoves[i], .T. ) // .T. = Simulation mode (same turn color for check test)
// Check if own king is in check in the new state
// Note: MakeMove normally toggles turn. For checking legality, we need to check if the
// player who MOVED is now in check. MakeMove returns FEN with NEXT turn.
// So if White moved, cNextFEN has Black to move.
// We need to check if White King is attacked by Black pieces in cNextFEN.
if !IsCheck( cNextFEN, cTurn )
AAdd( aLegalMoves, aPseudoMoves[i] )
endif
next
return aLegalMoves
function IsCheck( cFEN, cColor ) // cColor is the King's color we are checking
local aGrid := FENToGrid( cFEN )
local r, c, cPiece
local nKingRow, nKingCol
local lFound := .F.
local cHtml := ""
// Find King
for r := 1 to 8
for c := 1 to 8
cPiece := aGrid[r,c]
if !Empty(cPiece) .and. Upper(cPiece) == "K" .and. ;
( (cColor == "w" .and. IsUpperLocal(cPiece)) .or. (cColor == "b" .and. !IsUpperLocal(cPiece)) )
nKingRow := r
nKingCol := c
lFound := .T.
exit
endif
next
if lFound; exit; endif
next
if !lFound; return .F.; endif // Should not happen
// Check if King square is attacked by Enemy
return IsSquareAttacked( aGrid, nKingRow, nKingCol, cColor )
function IsSquareAttacked( aGrid, nRow, nCol, cDefendingColor )
local r, c, i, cPiece, cType, cEnemyColor
local aDirs, aOffs
// Check attacks from all directions
// 1. Pawn Attacks
// If I am White, check if Black pawns are at (Row-1, Col+/-1) attacking me
// Actually, White King at Row X. Black Pawn at Row X-1 (visually "above" if White is bottom-up, but Grid Row 1 is Top).
// Grid: Row 1=Rank 8 (Black side), Row 8=Rank 1 (White side).
// White Pawn moves r->r-1. Captures at r-1.
// Black Pawn moves r->r+1. Captures at r+1.
if cDefendingColor == "w"
// Check for Black Pawns at r-1, c+/-1
r := nRow - 1
if r >= 1
if nCol > 1 .and. aGrid[r, nCol-1] == "p"; return .T.; endif
if nCol < 8 .and. aGrid[r, nCol+1] == "p"; return .T.; endif
endif
else
// Check for White Pawns at r+1, c+/-1
r := nRow + 1
if r <= 8
if nCol > 1 .and. aGrid[r, nCol-1] == "P"; return .T.; endif
if nCol < 8 .and. aGrid[r, nCol+1] == "P"; return .T.; endif
endif
endif
// 2. Knights
aOffs := { {-2,-1}, {-2,1}, {-1,-2}, {-1,2}, {1,-2}, {1,2}, {2,-1}, {2,1} }
for i := 1 to 8
r := nRow + aOffs[i][1]
c := nCol + aOffs[i][2]
if IsOnBoard(r, c)
cPiece := aGrid[r,c]
if !Empty(cPiece)
// Enemy Knight?
if Upper(cPiece) == "N" .and. !IsOwnPiece(cPiece, cDefendingColor)
return .T.
endif
endif
endif
next
// 3. Sliding Pieces (Rook, Bishop, Queen)
// Straight (Rook/Queen)
aDirs := { {-1,0}, {1,0}, {0,-1}, {0,1} }
for i := 1 to 4
r := nRow + aDirs[i][1]
c := nCol + aDirs[i][2]
while IsOnBoard(r, c)
cPiece := aGrid[r,c]
if !Empty(cPiece)
if !IsOwnPiece(cPiece, cDefendingColor)
cType := Upper(cPiece)
if cType == "R" .or. cType == "Q"
return .T.
endif
endif
exit // Blocked
endif
r += aDirs[i][1]
c += aDirs[i][2]
end
next
// Diagonal (Bishop/Queen)
aDirs := { {-1,-1}, {-1,1}, {1,-1}, {1,1} }
for i := 1 to 4
r := nRow + aDirs[i][1]
c := nCol + aDirs[i][2]
while IsOnBoard(r, c)
cPiece := aGrid[r,c]
if !Empty(cPiece)
if !IsOwnPiece(cPiece, cDefendingColor)
cType := Upper(cPiece)
if cType == "B" .or. cType == "Q"
return .T.
endif
endif
exit // Blocked
endif
r += aDirs[i][1]
c += aDirs[i][2]
end
next
// 4. Enemy King (King cannot be next to King)
for r := nRow - 1 to nRow + 1
for c := nCol - 1 to nCol + 1
if IsOnBoard(r,c) .and. (r != nRow .or. c != nCol)
cPiece := aGrid[r,c]
if !Empty(cPiece) .and. Upper(cPiece) == "K" .and. !IsOwnPiece(cPiece, cDefendingColor)
return .T.
endif
endif
next
next
return .F.
function MakeMove( cFEN, cFrom, cTo, lSimulate )
local aGrid := FENToGrid( cFEN )
local aFrom := SquareToCoords( cFrom )
local aTo := SquareToCoords( cTo )
local cPiece := aGrid[ aFrom[1], aFrom[2] ]
local cTurn := GetTurn( cFEN )
local cNextTurn := iif( cTurn == "w", "b", "w" )
// Move piece
aGrid[ aTo[1], aTo[2] ] := cPiece
aGrid[ aFrom[1], aFrom[2] ] := ""
// TODO: Handle Pawn Promotion, Castling, En Passant logic updates
// For now, simple movement
return GridToFEN( aGrid ) + " " + cNextTurn + " KQkq - 0 1" // Simply toggle turn, preserve rights for now
//----------------------------------------------------------------------------//
function GetPseudoValidMoves( cFEN, cFrom )
local aMoves := {}
local aGrid := FENToGrid( cFEN )
local aFrom := SquareToCoords( cFrom )
local nRow := aFrom[1]
local nCol := aFrom[2]
local cPiece := aGrid[nRow, nCol]
local cType := Upper( cPiece )
local cColor := iif( IsUpperLocal(cPiece), "w", "b" )
local i, j, r, c
local nDir, nStartRow, nTemp
local aCapCols, aOffs, aDirs
if Empty(cPiece); return {}; endif
// Simplified Move Logic (No check validation yet)
do case
case cType == "P" // Pawn
nDir := iif( cColor == "w", -1, 1 ) // White moves up (row decrements)
nStartRow := iif( cColor == "w", 7, 2 )
// Move forward 1
r := nRow + nDir
if IsOnBoard(r, nCol) .and. Empty( aGrid[r, nCol] )
AAdd( aMoves, CoordsToSquare(r, nCol) )
// Move forward 2
if nRow == nStartRow
r := nRow + (nDir * 2)
if IsOnBoard(r, nCol) .and. Empty( aGrid[r, nCol] )
AAdd( aMoves, CoordsToSquare(r, nCol) )
endif
endif
endif
// Captures
aCapCols := { nCol - 1, nCol + 1 }
for i := 1 to 2
c := aCapCols[i]
r := nRow + nDir
if IsOnBoard(r, c)
if !Empty( aGrid[r,c] ) .and. !IsOwnPiece( aGrid[r,c], cColor )
AAdd( aMoves, CoordsToSquare(r, c) )
endif
endif
next
case cType == "N" // Knight
aOffs := { {-2,-1}, {-2,1}, {-1,-2}, {-1,2}, {1,-2}, {1,2}, {2,-1}, {2,1} }
for i := 1 to Len(aOffs)
r := nRow + aOffs[i][1]
c := nCol + aOffs[i][2]
if IsOnBoard(r, c)
if Empty( aGrid[r,c] ) .or. !IsOwnPiece( aGrid[r,c], cColor )
AAdd( aMoves, CoordsToSquare(r, c) )
endif
endif
next
case cType == "R" .or. cType == "B" .or. cType == "Q" // Sliders
aDirs := {}
if cType == "R" .or. cType == "Q" // Rook dirs
AAdd( aDirs, {-1, 0} ); AAdd( aDirs, {1, 0} ); AAdd( aDirs, {0, -1} ); AAdd( aDirs, {0, 1} )
endif
if cType == "B" .or. cType == "Q" // Bishop dirs
AAdd( aDirs, {-1, -1} ); AAdd( aDirs, {-1, 1} ); AAdd( aDirs, {1, -1} ); AAdd( aDirs, {1, 1} )
endif
for i := 1 to Len(aDirs)
r := nRow + aDirs[i][1]
c := nCol + aDirs[i][2]
while IsOnBoard(r, c)
if Empty( aGrid[r,c] )
AAdd( aMoves, CoordsToSquare(r, c) )
else
if !IsOwnPiece( aGrid[r,c], cColor )
AAdd( aMoves, CoordsToSquare(r, c) )
endif
exit // Blocked
endif
r += aDirs[i][1]
c += aDirs[i][2]
end
next
case cType == "K" // King
for r := nRow - 1 to nRow + 1
for c := nCol - 1 to nCol + 1
if IsOnBoard(r, c) .and. (r != nRow .or. c != nCol)
if Empty( aGrid[r,c] ) .or. !IsOwnPiece( aGrid[r,c], cColor )
AAdd( aMoves, CoordsToSquare(r, c) )
endif
endif
next
next
endcase
return aMoves
function IsOnBoard( r, c )
return r >= 1 .and. r <= 8 .and. c >= 1 .and. c <= 8
//----------------------------------------------------------------------------//
// FEN/Grid Helpers
function FENToGrid( cFEN )
local aGrid[8,8]
local aParts, cBoard, aRows, cRow
local i, j, r, c, k, ch, nCount
aParts := hb_ATokens( cFEN, " " )
cBoard := aParts[1]
aRows := hb_ATokens( cBoard, "/" )
for r := 1 to 8
cRow := aRows[r]
c := 1
for k := 1 to Len(cRow)
ch := SubStr( cRow, k, 1 )
if IsDigit( ch )
nCount := Val( ch )
for j := 1 to nCount
aGrid[r, c] := ""
c++
next
else
aGrid[r, c] := ch
c++
endif
next
next
return aGrid
function GridToFEN( aGrid )
local cFEN := ""
local r, c, i, nEmpty
for r := 1 to 8
nEmpty := 0
for c := 1 to 8
if Empty( aGrid[r,c] )
nEmpty++
else
if nEmpty > 0
cFEN += AllTrim(Str(nEmpty))
nEmpty := 0
endif
cFEN += aGrid[r,c]
endif
next
if nEmpty > 0
cFEN += AllTrim(Str(nEmpty))
endif
if r < 8; cFEN += "/"; endif
next
return cFEN
function SquareToCoords( cSquare )
local cCol := Left( cSquare, 1 )
local cRow := Right( cSquare, 1 )
local nCol := Asc( cCol ) - Asc("a") + 1
local nRow := 8 - (Val( cRow ) - 1)
// Matrix: Row 1 is Rank 8 (Top), Row 8 is Rank 1 (Bottom)
return { nRow, nCol }
function CoordsToSquare( r, c )
local cCol := Chr( Asc("a") + c - 1 )
local cRow := AllTrim(Str( 8 - (r - 1) ))
return cCol + cRow
//----------------------------------------------------------------------------//
// Rendering
function RenderBoard( cFEN, cSelected, aValidMoves, lInCheck, hConfig, lFlip )
local cHtml := ""
local nRank, nFile
local lIsWhite := iif( lFlip == NIL, .T., !lFlip ) // Default perspective White unless lFlip
local cPiece, cColorClass, cContent
local aGrid := FENToGrid( cFEN )
local lShowCoords := iif( HB_HHasKey(hConfig, "ShowCoords"), hConfig["ShowCoords"], .F. )
local cCoordsMode := iif( HB_HHasKey(hConfig, "CoordsMode"), hConfig["CoordsMode"], "Normal" )
// Logic for Coords Visibility
local lShowCoordsLeft := lShowCoords
local lShowCoordsRight := lShowCoords .and. ( cCoordsMode == "Full" )
local lShowCoordsTop := lShowCoords .and. ( cCoordsMode == "Full" )
local lShowCoordsBottom := lShowCoords
local cTextColor := hConfig["TextColor"]
cHtml += '<style>.blink-red { animation: blinker 1s linear infinite; background-color: red !important; } @keyframes blinker { 50% { opacity: 0.5; } }'
if HB_HHasKey( hConfig, "SquareWhite" )
cHtml += ' .white-square { background-color: ' + hConfig["SquareWhite"] + ' !important; }'
cHtml += ' .black-square { background-color: ' + hConfig["SquareBlack"] + ' !important; }'
endif
cHtml += ' .coord-cell { color: ' + cTextColor + ' !important; }'
cHtml += ' .player-info { color: ' + cTextColor + ' !important; }'
cHtml += ' .square svg { width: 100%; height: 100%; }'
cHtml += '</style>'
// Wrapper for Board Only (Grid)
cHtml += '<div class="chess-board-wrapper' + iif( hConfig["Background"]=="wood", " wood-bg", "" ) + '">'
// We will return HTML with:
// <div class="player-top">...</div>
// <div class="board-wrapper">...</div>
// <div class="player-bottom">...</div>
// But wait, RenderBoard returns just the HTML string to be injected.
// The View has structure:
// <form> ... {{board_html}} ... </form>
// So returning 3 divs is fine, but we need to ensure styling handles them.
// Wait, current CSS expects wrapper to be the grid.
// Better:
// <div style="display:flex; flex-direction:column; align-items:center;">
// <div class="player-info">Player 2</div>
// <div class="board-frame-grid"> Use Grid here for Coords+Board </div>
// <div class="player-info">Player 1</div>
// </div>
// Reset cHtml to build this structure
cHtml := ""
cHtml += '<style>.blink-red { animation: blinker 1s linear infinite; background-color: red !important; } @keyframes blinker { 50% { opacity: 0.5; } }'
if HB_HHasKey( hConfig, "SquareWhite" )
cHtml += ' .white-square { background-color: ' + hConfig["SquareWhite"] + ' !important; }'
cHtml += ' .black-square { background-color: ' + hConfig["SquareBlack"] + ' !important; }'
endif
cHtml += ' .coord-cell { color: ' + cTextColor + ' !important; font-weight:bold; }' // Bold coords
cHtml += ' .player-info { color: ' + cTextColor + ' !important; width: 100%; display: flex; align-items: center; gap: 10px; margin: 5px 0; font-weight: bold; }'
cHtml += ' .player-info img { width: 30px; height: 30px; border-radius: 4px; background-color: rgba(255,255,255,0.2); }'
cHtml += ' .square svg { width: 100%; height: 100%; }'
cHtml += '</style>'
// Container
cHtml += '<div style="display:flex; flex-direction:column; align-items:flex-start; width: fit-content; margin: 0 auto;">'
// PLAYER 2 (Top - Black usually, or Opponent)
cHtml += '<div class="player-info">'
cHtml += '<img src="players/black_400.png" alt="Black"><span>' + iif(hb_HHasKey(hConfig,"Player2"),hConfig["Player2"],"Giocatore 2") + '</span>'
cHtml += '</div>'
// BOARD FRAME (The "Wood" part with coordinates)
cHtml += '<div class="chess-board-wrapper' + iif( hConfig["Background"]=="wood", " wood-bg", "" ) + '">'
// Flex Container for Center (Left Coords | Board | Right Gutter)
cHtml += '<div style="display:flex; flex-direction:row; align-items: flex-start;">'
// 1. LEFT COORDS (Numbers) - Width 20px
cHtml += '<div style="display:grid; grid-template-rows: repeat(8, 50px); width: 20px; align-items: center; justify-items: center; margin-right: 5px;">'
if lShowCoords
for nRank := 1 to 8
cContent := iif( lIsWhite, AllTrim(Str(9-nRank)), AllTrim(Str(nRank)) )
cHtml += '<div class="coord-cell">' + cContent + '</div>'
next
endif
cHtml += '</div>'
// 2. BOARD (The Squares)
cHtml += '<div class="board-container" style="border: 5px solid #4a3021;">'
// Board
cHtml += '<div class="chess-board">'
if lInCheck == nil; lInCheck := .F.; endif
cTurn := GetTurn( cFEN )
for nRank := 1 to 8
for nFile := 1 to 8
// Calculate Grid Coordinates
r := iif( lIsWhite, nRank, 9 - nRank )
c := iif( lIsWhite, nFile, 9 - nFile )
cSquare := CoordsToSquare( r, c )
cPiece := aGrid[r, c]
// Background color (Visual checkerboard depends on nRank+nFile)
if (nRank + nFile) % 2 == 0
cClass := "white-square"
else
cClass := "black-square"
endif
// Selection highlight
if cSquare == cSelected
cClass += " selected"
endif
// King Check Highlight
if lInCheck .and. !Empty(cPiece) .and. Upper(cPiece) == "K"
// Check if it's the current turn's king
if (cTurn == "w" .and. IsUpperLocal(cPiece)) .or. (cTurn == "b" .and. !IsUpperLocal(cPiece))
cClass += " blink-red"
endif
endif
// Piece
if !Empty(cPiece)
cContent := GetPieceSymbol( cPiece, hConfig["Theme"] )
cColorClass := iif( IsUpperLocal(cPiece), " piece-white", " piece-black" )
else
cContent := ""
cColorClass := ""
endif
// Valid move marker
lIsValid := .F.
if ASCAN( aValidMoves, cSquare ) > 0
cClass += " valid-move"
lIsValid := .T.
endif
cHtml += '<div class="square ' + cClass + '" '
cHtml += 'onclick="selectSquare(' + Chr(39) + cSquare + Chr(39) + ')">'
if !Empty(cContent)
cHtml += '<span class="piece' + cColorClass + '">' + cContent + '</span>'
endif
if lIsValid .and. Empty(cContent)
cHtml += '<div class="move-dot"></div>'
endif
cHtml += '</div>'
next
next
cHtml += '</div>' // End chess-board (grid)
cHtml += '</div>' // End board-container
// 3. RIGHT GUTTER (Settings Icon)
cHtml += '<div style="width: 30px; display: flex; flex-direction: column; height: 400px; padding-left: 5px; border-left: 0px solid transparent;">'
// Icon aligned with Rank 8 (Top)
cHtml += '<div style="height: 50px; display: flex; align-items: center; justify-content: center;">'
cHtml += '<img src="board/setup.png" style="cursor:pointer; width:20px;" title="Impostazioni" onclick="openSettings()">'
cHtml += '</div>'
cHtml += '</div>'
cHtml += '</div>' // End Center Flex Container
// Bottom Coords (Letters) - Needs left padding to align with board
cHtml += '<div style="display:flex; height: 20px; align-items: center; justify-content: flex-start; margin-left: 25px;">' // 20px Left Coords + 5px margin
cHtml += '<div style="display:grid; grid-template-columns: repeat(8, 50px); justify-items: center;">'
if lShowCoords
for nFile := 1 to 8
// If White: a..h. Black: h..a
cContent := iif( lIsWhite, Chr(96+nFile), Chr(96+(9-nFile)) )
cHtml += '<div class="coord-cell">' + cContent + '</div>'
next
endif
cHtml += '</div>'
cHtml += '</div>'
cHtml += '</div>' // End chess-board-wrapper
// Bottom Player
cHtml += '<div class="player-info">'
cHtml += '<img src="players/white_400.png" alt="White"><span>' + iif(hb_HHasKey(hConfig,"Player1"),hConfig["Player1"],"Giocatore 1") + '</span>'
cHtml += '</div>'
cHtml += '</div>' // End Container wrapper
return cHtml
function GetPieceSymbol( cPiece, cTheme )
local cColor := iif( IsUpperLocal(cPiece), "w", "b" )
local cType := Lower( cPiece ) // Always match filename logic: p, n, b, r, q, k
local cFile
if !Empty( cTheme ) .and. Upper(cTheme) != "DEFAULT"
// Inline SVG Logic to support 'currentColor' and avoid 404 path issues
// File: c:\hix\board\themes\<Theme>\<color><type>.svg
cFile := "c:\hix\board\themes\" + cTheme + "\" + cColor + cType + ".svg"
if File( cFile )
return MemoRead( cFile )
endif
endif
// Default Generic Glyphs
do case
case Upper(cPiece) == "P"
return "♟" // User wanted simple PRG, using generic chess glyphs
case Upper(cPiece) == "R"
return "♜"
case Upper(cPiece) == "N"
return "♞"
case Upper(cPiece) == "B"
return "♝"
case Upper(cPiece) == "Q"
return "♛"
case Upper(cPiece) == "K"
return "♚"
endcase
return cPiece
//----------------------------------------------------------------------------//
// View Helper (Standard HIX pattern)
function View( cName, hParams )
local cData
local cViewPath := "c:\hix\views\" + cName + ".view"
if hParams == nil; hParams := {=>}; endif
if File( cViewPath )
cData = MemoRead( cViewPath )
while ReplaceBlocks( @cData, "{{", "}}", hParams )
end
else
cData = "<h2>" + cName + " not found!</h2>"
endif
return cData
function ReplaceBlocks( cCode, cStartBlock, cEndBlock, hParams )
local nStart, nEnd, cBlock, cParamName
local lReplaced := .F.
while ( nStart := At( cStartBlock, cCode ) ) != 0 .and. ( nEnd := At( cEndBlock, cCode ) ) != 0
cBlock = SubStr( cCode, nStart + Len( cStartBlock ), nEnd - nStart - Len( cEndBlock ) )
cParamName := AllTrim( cBlock )
if hb_HHasKey( hParams, cParamName )
cCode = SubStr( cCode, 1, nStart - 1 ) + ValToChar( hParams[ cParamName ] ) + SubStr( cCode, nEnd + Len( cEndBlock ) )
else
cCode = SubStr( cCode, 1, nStart - 1 ) + "" + SubStr( cCode, nEnd + Len( cEndBlock ) )
endif
lReplaced = .T.
end
return lReplaced
//----------------------------------------------------------------------------//
// Configuration
function LoadSettings()
local hConfig := {=>}
local cIniFile := "c:\hix\chess.ini"
// Defaults
hConfig["SquareWhite"] := "#f0d9b5"
hConfig["SquareBlack"] := "#b58863"
hConfig["Background"] := "default" // or 'wood'
hConfig["ShowCoords"] := .T.
hConfig["CoordsMode"] := "Normal"
hConfig["Theme"] := "Alpha"
hConfig["TextColor"] := "#000000"
hConfig["Language"] := "italian"
if File( cIniFile )
// Simple parsing assuming standard format
// Note: In real Harbor use hb_IniRead, assuming manual for portability in this snippet
hConfig["SquareWhite"] := GetIniVal( cIniFile, "config", "SquareWhite", hConfig["SquareWhite"] )
hConfig["SquareBlack"] := GetIniVal( cIniFile, "config", "SquareBlack", hConfig["SquareBlack"] )
hConfig["Background"] := GetIniVal( cIniFile, "config", "Background", hConfig["Background"] )
hConfig["ShowCoords"] := ( GetIniVal( cIniFile, "config", "ShowCoords", "T" ) == "T" )
hConfig["CoordsMode"] := GetIniVal( cIniFile, "config", "CoordsMode", "Normal" )
hConfig["Theme"] := GetIniVal( cIniFile, "config", "Theme", hConfig["Theme"] )
hConfig["TextColor"] := GetIniVal( cIniFile, "config", "TextColor", hConfig["TextColor"] )
hConfig["Language"] := GetIniVal( cIniFile, "config", "Language", hConfig["Language"] )
hConfig["Player1"] := GetIniVal( cIniFile, "config", "Player1", "Giocatore 1" )
hConfig["Player2"] := GetIniVal( cIniFile, "config", "Player2", "Giocatore 2" )
hConfig["Level"] := GetIniVal( cIniFile, "config", "Level", "1" )
endif
return hConfig
function SaveSettings( hConfig )
local cIniFile := "c:\hix\chess.ini"
local cData := "[config]" + hb_Eol()
cData += "SquareWhite=" + hConfig["SquareWhite"] + hb_Eol()
cData += "SquareBlack=" + hConfig["SquareBlack"] + hb_Eol()
cData += "Background=" + hConfig["Background"] + hb_Eol()
cData += "ShowCoords=" + iif( hConfig["ShowCoords"], "T", "F" ) + hb_Eol()
cData += "CoordsMode=" + hConfig["CoordsMode"] + hb_Eol()
cData += "Theme=" + hConfig["Theme"] + hb_Eol()
cData += "TextColor=" + hConfig["TextColor"] + hb_Eol()
cData += "Language=" + hConfig["Language"] + hb_Eol()
// Persist Players & Level
if hb_HHasKey(hConfig, "Player1"); cData += "Player1=" + hConfig["Player1"] + hb_Eol(); endif
if hb_HHasKey(hConfig, "Player2"); cData += "Player2=" + hConfig["Player2"] + hb_Eol(); endif
if hb_HHasKey(hConfig, "Level"); cData += "Level=" + hConfig["Level"] + hb_Eol(); endif
MemoWrit( cIniFile, cData )
return nil
function GetIniVal( cFile, cSection, cKey, cDefault )
local cData := MemoRead( cFile )
local aLines := hb_ATokens( cData, hb_Eol() )
local i, cLine, nPos
// Very basic parser
for i := 1 to Len(aLines)
cLine := AllTrim( aLines[i] )
if Left(cLine, Len(cKey)+1) == cKey + "="
return SubStr( cLine, Len(cKey)+2 )
endif
next
return cDefault
function ValToChar( u )
local cType := ValType( u )
if cType == "C"; return u; endif
if cType == "N"; return AllTrim(Str(u)); endif
if cType == "D"; return DtoS(u); endif
if cType == "A"; return "{...}"; endif
if cType == "O"; return "[Object]"; endif
if cType == "L"; return iif(u, ".T.", ".F."); endif
if cType == "U"; return "NIL"; endif
return ""
function GetThemes()
local aThemes := {}
local aDir := Directory( "c:\hix\board\themes\*.*", "D" )
local i
for i := 1 to Len( aDir )
if aDir[i][5] == "D" // Check Attribute if strictly directory, simpler:
// Directory() returns array of subarrays. [1]=Name, [5]=Attr
// "D" attribute check.
if aDir[i][1] != "." .and. aDir[i][1] != ".."
if IsDirectory( "c:\hix\board\themes\" + aDir[i][1] )
AAdd( aThemes, aDir[i][1] )
endif
endif
endif
next
return aThemes
function GetThemesOptions( cSelected )
local aThemes := GetThemes()
local i, cHtml := '<option value="Default">Default (Glyphs)</option>'
local cName, cSel
for i := 1 to Len(aThemes)
cName := aThemes[i]
cSel := iif( Upper(cName) == Upper(cSelected), "selected", "" )
cHtml += '<option value="' + cName + '" ' + cSel + '>' + cName + '</option>'
next
return cHtml
function UseSavedGames()
if File("c:\hix\mosse\saved.dbf")
Use "c:\hix\mosse\saved.dbf" shared new alias "SAVED"
return .T.
endif
return .F.
//----------------------------------------------------------------------------//
// AI Logic
function GetComputerMove( cFEN, nLevel, cColor )
local aMoves := GetAllLegalMoves( cFEN, cColor )
local nBestScore := -9999
local cBestMove := ""
local cMove, cTarget, nScore, i
local cMyLowColor := Lower(cColor) // 'w' or 'b'
if Len(aMoves) == 0; return ""; endif
// Level 1: Pure Random
if nLevel <= 1
return aMoves[ HB_RandomInt( 1, Len(aMoves) ) ]
endif
// Level 2-9: Basic Evaluation (Material)
// Very simple: Try each move, check captured piece value
// Improve: Minimax for higher levels
for i := 1 to Len(aMoves)
cMove := aMoves[i] // "e2e4"
// Analyze target square content *before* move for simple capture logic
// Or Simulate Move and Evaluate Board
// Simple 1-ply capture logic
cTarget := GetPieceAt( cFEN, Right(cMove, 2) )
nScore := 0
if !Empty( cTarget )
// Capture!
nScore += GetPieceValue( cTarget ) * 10
endif
// Add randomness for lower levels so it's not deterministic
if nLevel < 5
nScore += HB_RandomInt( 0, 5 )
endif
// Checkmate priority (Higher levels)
// if nLevel >= 5 ... Simulate and check IsCheckmate
if nScore > nBestScore
nBestScore := nScore
cBestMove := cMove
elseif nScore == nBestScore
// Randomly pick between equal moves
if HB_RandomInt(0, 1) == 1
cBestMove := cMove
endif
endif
next
return cBestMove
function GetAllLegalMoves( cFEN, cColor )
local aAllMoves := {}
local r, c, cSquare, cPiece
local aMoves, i
local aGrid := FENToGrid( cFEN )
for r := 1 to 8
for c := 1 to 8
cPiece := aGrid[r, c]
if !Empty(cPiece) .and. IsOwnPiece( cPiece, cColor )
cSquare := CoordsToSquare( r, c )
aMoves := GetLegalMoves( cFEN, cSquare ) // Returns array of Target Squares
for i := 1 to Len(aMoves)
AAdd( aAllMoves, cSquare + aMoves[i] ) // "e2e4"
next
endif
next
next
return aAllMoves
function GetPieceValue( cPiece )
local cType := Upper( cPiece )
if cType == "P"; return 1; endif
if cType == "N"; return 3; endif
if cType == "B"; return 3; endif
if cType == "R"; return 5; endif
if cType == "Q"; return 9; endif
return 0
function IsUpperLocal( cChar )
local nAsc := Asc( cChar )
return nAsc >= 65 .and. nAsc <= 90
//----------------------------------------------------------------------------//
// Language Utilities
function LoadLanguage( cLang )
local cFile := "c:\hix\lang\" + Lower(cLang) + ".lng"
local hLang := {=>}
local cLine, aTokens, cKey, cValue
local nHandle, cContent
// Default to Italian if file not found
if !File( cFile )
cFile := "c:\hix\lang\italian.lng"
endif
if !File( cFile )
// Return empty hash if no language files exist
return hLang
endif
// Read file line by line
nHandle := FOpen( cFile, 0 ) // Read mode
if nHandle != -1
cContent := Space( 10000 )
FRead( nHandle, @cContent, 10000 )
FClose( nHandle )
// Parse lines
aTokens := hb_ATokens( cContent, Chr(13) + Chr(10) )
for each cLine in aTokens
cLine := AllTrim( cLine )
// Skip comments and empty lines
if Empty(cLine) .or. Left(cLine, 2) == "//"
loop
endif
// Parse KEY=Value
if "=" $ cLine
cKey := AllTrim( SubStr( cLine, 1, At("=", cLine) - 1 ) )
cValue := AllTrim( SubStr( cLine, At("=", cLine) + 1 ) )
hLang[ cKey ] := cValue
endif
next
endif
return hLang
function T( cKey, hLang )
// Translation function - returns translated text or key if not found
if hb_HHasKey( hLang, cKey )
return hLang[ cKey ]
endif
return cKey // Fallback to key itself
function GetLanguageOptions( cCurrentLang )
local cHtml := ""
local aLangs := { {"italian", "Italiano"}, {"english", "English"}, {"spanish", "Español"}, {"german", "Deutsch"} }
local aLang
for each aLang in aLangs
cHtml += '<option value="' + aLang[1] + '"'
if Lower(cCurrentLang) == aLang[1]
cHtml += ' selected'
endif
cHtml += '>' + aLang[2] + '</option>'
next
return cHtmlchess.ini
[config]
SquareWhite=#f0d9b5
SquareBlack=#b58863
Background=wood
ShowCoords=T
CoordsMode=Normal
Theme=Balestegui
TextColor=#5da4c4
Language=spanish
Player1=Silvio
Player2=Antonio
Level=0make lang folder
italian.lng
// Italian Language File
// Format: KEY=Value
// Top Menu
NEW_GAME=Nuova Partita
LOAD_GAME=Carica Partita
SAVE_GAME=Salva Partita
EXIT=Uscita
// Settings Dialog
SETTINGS_TITLE=Impostazioni
PIECES=Pezzi
BOARD=Scacchiera
BOARD_DEFAULT=Grigio (Default)
BOARD_WOOD=Legno con nodi
SHOW_COORDS=Mostra coordinate scacchiera
COORDS_OUTSIDE=All'esterno
COORDS_FULL=Completa
PIECE_NOTATION=Notazione pezzi
ICONS=Icone
TEXT_COLOR=Colore Testo & Coordinate
CANCEL=Annulla
SAVE=Salva
// New Game Dialog
NEW_GAME_TITLE=Nuova Partita
GAME_TYPE=Tipo di Gioco
HUMAN_VS_HUMAN=Umano vs Umano
HUMAN_VS_PC=Umano vs PC
PLAYER1=Giocatore 1 (Tu)
PLAYER2=Giocatore 2
AI_LEVEL=Livello AI
YOUR_COLOR=Il tuo Colore
WHITE=Bianco
BLACK=Nero
START=Inizia
// AI Levels
LEVEL_1=Neofita
LEVEL_2=Principiante
LEVEL_3=Intermedio
LEVEL_4=Intermedio II
LEVEL_5=Avanzato
LEVEL_6=Esperto
LEVEL_7=Maestro
LEVEL_8=Grande Maestro
LEVEL_9=Massimo
// Save Game Dialog
SAVE_GAME_TITLE=Salva Partita
GAME_NAME=Nome Partita
ENTER_NAME=Inserisci nome...
// Load Game Dialog
LOAD_GAME_TITLE=Carica Partita
CLOSE=Chiudi
// Game Messages
CHECKMATE=SCACCO MATTO!
CHECK=SCACCO!
WINS=Vince!
COMPUTER_THINKING=Computer sta pensando...
ENTER_NAME_ALERT=Inserisci un nome!
GAME_SAVED=Partita Salvata
GAME_LOADED=Partita Caricata
// History
HISTORY=Cronologia
// Default Player Names
DEFAULT_PLAYER1=Giocatore 1
DEFAULT_PLAYER2=Giocatore 2
COMPUTER=Computer
LEVEL_SHORT=Liv.spanish.lng
// Spanish Language File
// Format: KEY=Value
// Top Menu
NEW_GAME=Nueva Partida
LOAD_GAME=Cargar Partida
SAVE_GAME=Guardar Partida
EXIT=Salir
// Settings Dialog
SETTINGS_TITLE=Configuración
PIECES=Piezas
BOARD=Tablero
BOARD_DEFAULT=Gris (Predeterminado)
BOARD_WOOD=Madera con nudos
SHOW_COORDS=Mostrar coordenadas del tablero
COORDS_OUTSIDE=Exterior
COORDS_FULL=Completa
PIECE_NOTATION=Notación de piezas
ICONS=Iconos
TEXT_COLOR=Color de Texto y Coordenadas
CANCEL=Cancelar
SAVE=Guardar
// New Game Dialog
NEW_GAME_TITLE=Nueva Partida
GAME_TYPE=Tipo de Juego
HUMAN_VS_HUMAN=Humano vs Humano
HUMAN_VS_PC=Humano vs PC
PLAYER1=Jugador 1 (Tú)
PLAYER2=Jugador 2
AI_LEVEL=Nivel IA
YOUR_COLOR=Tu Color
WHITE=Blanco
BLACK=Negro
START=Comenzar
// AI Levels
LEVEL_1=Novato
LEVEL_2=Principiante
LEVEL_3=Intermedio
LEVEL_4=Intermedio II
LEVEL_5=Avanzado
LEVEL_6=Experto
LEVEL_7=Maestro
LEVEL_8=Gran Maestro
LEVEL_9=Máximo
// Save Game Dialog
SAVE_GAME_TITLE=Guardar Partida
GAME_NAME=Nombre de Partida
ENTER_NAME=Ingrese nombre...
// Load Game Dialog
LOAD_GAME_TITLE=Cargar Partida
CLOSE=Cerrar
// Game Messages
CHECKMATE=¡JAQUE MATE!
CHECK=¡JAQUE!
WINS=¡Gana!
COMPUTER_THINKING=Computadora pensando...
ENTER_NAME_ALERT=¡Ingrese un nombre!
GAME_SAVED=Partida Guardada
GAME_LOADED=Partida Cargada
// History
HISTORY=Historial
// Default Player Names
DEFAULT_PLAYER1=Jugador 1
DEFAULT_PLAYER2=Jugador 2
COMPUTER=Computadora
LEVEL_SHORT=Nv.english.lng
// Spanish Language File
// Format: KEY=Value
// Top Menu
NEW_GAME=Nueva Partida
LOAD_GAME=Cargar Partida
SAVE_GAME=Guardar Partida
EXIT=Salir
// Settings Dialog
SETTINGS_TITLE=Configuración
PIECES=Piezas
BOARD=Tablero
BOARD_DEFAULT=Gris (Predeterminado)
BOARD_WOOD=Madera con nudos
SHOW_COORDS=Mostrar coordenadas del tablero
COORDS_OUTSIDE=Exterior
COORDS_FULL=Completa
PIECE_NOTATION=Notación de piezas
ICONS=Iconos
TEXT_COLOR=Color de Texto y Coordenadas
CANCEL=Cancelar
SAVE=Guardar
// New Game Dialog
NEW_GAME_TITLE=Nueva Partida
GAME_TYPE=Tipo de Juego
HUMAN_VS_HUMAN=Humano vs Humano
HUMAN_VS_PC=Humano vs PC
PLAYER1=Jugador 1 (Tú)
PLAYER2=Jugador 2
AI_LEVEL=Nivel IA
YOUR_COLOR=Tu Color
WHITE=Blanco
BLACK=Negro
START=Comenzar
// AI Levels
LEVEL_1=Novato
LEVEL_2=Principiante
LEVEL_3=Intermedio
LEVEL_4=Intermedio II
LEVEL_5=Avanzado
LEVEL_6=Experto
LEVEL_7=Maestro
LEVEL_8=Gran Maestro
LEVEL_9=Máximo
// Save Game Dialog
SAVE_GAME_TITLE=Guardar Partida
GAME_NAME=Nombre de Partida
ENTER_NAME=Ingrese nombre...
// Load Game Dialog
LOAD_GAME_TITLE=Cargar Partida
CLOSE=Cerrar
// Game Messages
CHECKMATE=¡JAQUE MATE!
CHECK=¡JAQUE!
WINS=¡Gana!
COMPUTER_THINKING=Computadora pensando...
ENTER_NAME_ALERT=¡Ingrese un nombre!
GAME_SAVED=Partida Guardada
GAME_LOADED=Partida Cargada
// History
HISTORY=Historial
// Default Player Names
DEFAULT_PLAYER1=Jugador 1
DEFAULT_PLAYER2=Jugador 2
COMPUTER=Computadora
LEVEL_SHORT=Nv.german.lng
// German Language File
// Format: KEY=Value
// Top Menu
NEW_GAME=Neues Spiel
LOAD_GAME=Spiel Laden
SAVE_GAME=Spiel Speichern
EXIT=Beenden
// Settings Dialog
SETTINGS_TITLE=Einstellungen
PIECES=Figuren
BOARD=Schachbrett
BOARD_DEFAULT=Grau (Standard)
BOARD_WOOD=Holz mit Knoten
SHOW_COORDS=Brettkoordinaten anzeigen
COORDS_OUTSIDE=Außen
COORDS_FULL=Vollständig
PIECE_NOTATION=Figurennotation
ICONS=Symbole
TEXT_COLOR=Text- & Koordinatenfarbe
CANCEL=Abbrechen
SAVE=Speichern
// New Game Dialog
NEW_GAME_TITLE=Neues Spiel
GAME_TYPE=Spieltyp
HUMAN_VS_HUMAN=Mensch vs Mensch
HUMAN_VS_PC=Mensch vs PC
PLAYER1=Spieler 1 (Du)
PLAYER2=Spieler 2
AI_LEVEL=KI-Stufe
YOUR_COLOR=Deine Farbe
WHITE=Weiß
BLACK=Schwarz
START=Starten
// AI Levels
LEVEL_1=Anfänger
LEVEL_2=Einsteiger
LEVEL_3=Fortgeschritten
LEVEL_4=Fortgeschritten II
LEVEL_5=Erweitert
LEVEL_6=Experte
LEVEL_7=Meister
LEVEL_8=Großmeister
LEVEL_9=Maximum
// Save Game Dialog
SAVE_GAME_TITLE=Spiel Speichern
GAME_NAME=Spielname
ENTER_NAME=Namen eingeben...
// Load Game Dialog
LOAD_GAME_TITLE=Spiel Laden
CLOSE=Schließen
// Game Messages
CHECKMATE=SCHACHMATT!
CHECK=SCHACH!
WINS=Gewinnt!
COMPUTER_THINKING=Computer denkt nach...
ENTER_NAME_ALERT=Bitte Namen eingeben!
GAME_SAVED=Spiel Gespeichert
GAME_LOADED=Spiel Geladen
// History
HISTORY=Verlauf
// Default Player Names
DEFAULT_PLAYER1=Spieler 1
DEFAULT_PLAYER2=Spieler 2
COMPUTER=Computer
LEVEL_SHORT=St.Make bitmaps folder
and insert top.png

create players folder and insert these two files


then there are many file svg as I told to antonio I cannot insert here
chess.view
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hix Chess</title>
{{View("style")}}
<style>
.chess-container {
display: none; /* Hidden strictly logic until splash done (handled by js) */
flex-direction: column;
align-items: center;
margin-top: 0px;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
width: 100%;
height: 100vh;
background-color: #e0e0e0;
}
#splash-screen {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background-color: #2d2d2d;
display: none; /* Default to hidden to prevent flash, JS or Conditional will show/hide */
justify-content: center;
align-items: center;
z-index: 9999;
}
#splash-screen img {
max-width: 80%;
max-height: 80%;
box-shadow: 0 0 50px rgba(0,0,0,0.8);
}
.button-bar {
width: 100%;
height: 70px;
background-color: #3e2723;
display: flex;
align-items: center;
justify-content: flex-start; /* Left aligned */
padding: 0 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
margin-bottom: 20px;
}
.btn-bar {
padding: 8px 15px;
background-color: #5d4037;
color: white;
border: 1px solid #795548;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-left: 10px;
}
.btn-bar:hover {
background-color: #4e342e;
}
/* Wrapper styling reduced, mostly handled by PRG structure */
/* Wrapper is now the Wood Frame */
.chess-board-wrapper {
display: inline-block;
background-color: #8b4513; /* Wood fallback */
padding: 15px; /* Spacing for coords inside frame */
border-radius: 4px;
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
position: relative;
border: 2px solid #5d4037;
}
.wood-bg {
background: repeating-linear-gradient(45deg, #8b4513 0, #8b4513 10px, #a0522d 10px, #a0522d 20px) !important;
}
.player-info {
display: flex;
align-items: center;
gap: 10px;
margin: 5px 0;
color: #333; /* Fallback */
font-weight: bold;
width: 100%; /* Ensure it spans */
}
.player-info img {
width: 30px;
height: 30px;
border-radius: 4px;
background-color: rgba(255,255,255,0.2);
}
.board-container {
display: flex;
flex-direction: row;
border: none; /* Border handled by chess-board or logic */
}
.coord-cell {
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
font-weight: bold;
color: #555;
}
.chess-board {
display: grid;
grid-template-columns: repeat(8, 50px);
grid-template-rows: repeat(8, 50px);
width: 400px;
height: 400px;
border: 2px solid #4a3021;
user-select: none;
}
.square {
width: 50px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
font-size: 35px; /* Adjust piece glyph size if needed, though SVG is 100% */
position: relative;
cursor: default;
}
.white-square {
background-color: #f0d9b5;
}
.black-square {
background-color: #b58863;
}
.selected {
background-color: #f5f682 !important;
}
.valid-move {
cursor: pointer;
}
.move-dot {
width: 20px;
height: 20px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 50%;
position: absolute;
pointer-events: none;
}
.square:hover .move-dot {
background-color: rgba(0, 0, 0, 0.4);
}
/* Piece Styles */
.piece {
font-size: 40px;
cursor: pointer;
z-index: 10;
/* Prevent text selection */
-webkit-user-select: none;
user-select: none;
}
.piece-white {
color: #fff;
text-shadow: 1px 1px 2px #000;
}
.piece-black {
color: #000;
text-shadow: 0px 0px 1px #fff; /* Slight halo for visibility on dark */
}
.controls {
margin-top: 20px;
display: flex;
gap: 15px;
align-items: center;
}
.btn {
padding: 10px 20px;
background-color: #5d4037;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn:hover {
background-color: #3e2723;
}
.status-panel {
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
display: none; /* Hidden as per redesign */
}
.settings-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #4e342e; /* "Marroncino" / Dark Brown to match theme */
padding: 0;
border-radius: 8px;
box-shadow: 0 10px 25px rgba(0,0,0,0.5);
z-index: 1000;
width: 400px;
font-family: "Segoe UI", sans-serif;
color: #e0e0e0;
border: 1px solid #5d4037;
display: none; /* Hidden by default */
}
color: #333;
}
.turn-indicator {
margin-left: 10px;
padding: 5px 10px;
background: #eee;
border-radius: 4px;
}
.wood-bg {
background-color: #8b4513;
/* CSS Wood Texture */
background: repeating-linear-gradient(45deg, #a0522d 0, #a0522d 10px, #8b4513 10px, #8b4513 20px);
border: 4px solid #5d4037;
box-shadow: inset 0 0 20px #3e2723;
}
.settings-header {
width: 100%;
height: 170px; /* Increased height to show more of the image */
background-image: url('bitmaps/top.png');
background-size: cover;
background-position: top; /* Show from the top of the image */
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.settings-content {
padding: 20px;
padding-top: 10px; /* Reduced top padding since header is larger */
}
.form-group {
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.form-group label {
font-size: 14px;
color: #bbb;
}
.form-control {
background: #3d3d3d;
border: 1px solid #555;
color: white;
padding: 8px;
border-radius: 4px;
width: 180px;
}
/* Switch Styles */
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
}
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background-color: #555;
transition: .4s;
border-radius: 20px;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 2px;
bottom: 2px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #4CAF50;
}
input:checked + .slider:before {
transform: translateX(20px);
}
.modal-actions {
display: flex;
justify-content: space-between;
padding: 15px 20px; /* Padding for footer */
background: #252525;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
.btn-cancel {
background: #444;
color: white;
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 45%;
}
.btn-save {
background: #4CAF50; /* Green */
color: white;
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 45%;
}
.coords-options {
margin-top: 10px;
background: #333;
padding: 10px;
border-radius: 4px;
}
.coords-radio {
display: flex;
gap: 10px;
font-size: 13px;
color: #aaa;
}
margin-top: 20px;
}
</style>
</head>
<body>
<!-- Splash Screen -->
<!-- Check if action is active. If so, FORCE style='display:none !important' -->
<div id="splash-screen" style="display: {{splash_style}};">
<img src="bitmaps/top.png" alt="HIX Chess">
</div>
<div class="chess-container" id="main-container">
<!-- Top Button Bar -->
<div class="button-bar">
<!-- Moved Game Controls Here -->
<button type="button" class="btn-bar" onclick="newGame()">{{L_NEW_GAME}}</button>
<button type="button" class="btn-bar" onclick="openLoadGame()">{{L_LOAD_GAME}}</button>
<button type="button" class="btn-bar" onclick="openSaveGame()">{{L_SAVE_GAME}}</button>
<div style="flex-grow: 1;"></div> <!-- Spacer -->
<button type="button" class="btn-bar" onclick="window.close()">{{L_EXIT}}</button>
</div>
<!-- Removed Header and Status Panel -->
<div style="display: flex; gap: 20px;">
<div>
<form id="chessForm" action="chess.prg" method="POST">
<input type="hidden" name="action" id="action" value="">
<input type="hidden" name="fen" id="fen" value="{{fen}}">
<input type="hidden" name="selected" id="selected" value="{{selected}}">
<input type="hidden" name="clicked_square" id="clicked_square" value="">
<input type="hidden" name="game_id" id="game_id" value="{{game_id}}">
<!-- Settings Fields (Hidden) -->
<input type="hidden" name="square_white" id="h_square_white">
<input type="hidden" name="square_black" id="h_square_black">
<input type="hidden" name="background" id="h_background">
<input type="hidden" name="theme" id="h_theme">
<input type="hidden" name="text_color" id="h_text_color">
<input type="hidden" name="show_coords" id="h_show_coords">
<input type="hidden" name="coords_mode" id="h_coords_mode">
<!-- Game State Persistence -->
<input type="hidden" name="player1" value="{{player1}}">
<input type="hidden" name="player2" value="{{player2}}">
<input type="hidden" name="game_mode" value="{{game_mode}}">
<input type="hidden" name="level" value="{{level}}">
<input type="hidden" name="my_color" value="{{my_color}}">
<!-- Board injected from PRG -->
{{board_html}}
</form>
</div>
<div class="moves-box" style="width: 250px; height: 480px; overflow-y: auto; border: 4px solid #8b4513; padding: 10px; background: #fffdf0; border-radius: 4px; box-shadow: 0 5px 15px rgba(0,0,0,0.5);">
<h3 style="margin-top:0; color:#5d4037; border-bottom: 2px solid #8b4513; padding-bottom:5px;">{{L_HISTORY}}</h3>
{{moves_list}}
</div>
</div>
</div>
<!-- Rewritten Settings Dialog -->
<div id="settingsModal" class="settings-modal">
<div class="settings-header"></div> <!-- Images via CSS -->
<form id="settingsForm" method="POST" action="chess.prg">
<input type="hidden" name="action" id="action_settings" value="save_settings">
<!-- Hidden inputs -->
<input type="hidden" name="square_white" id="h_square_white_dlg" value="{{cfg_white}}">
<input type="hidden" name="square_black" id="h_square_black_dlg" value="{{cfg_black}}">
<input type="hidden" name="background" id="h_background_dlg">
<input type="hidden" name="theme" id="h_theme_dlg">
<input type="hidden" name="show_coords" id="h_show_coords_dlg">
<input type="hidden" name="coords_mode" id="h_coords_mode_dlg">
<input type="hidden" name="language" id="h_language_dlg">
<input type="hidden" name="text_color" id="h_text_color_dlg" value="{{cfg_text}}">
<div class="settings-content">
<div class="form-group">
<label>{{L_PIECES}}</label>
<select id="dlg_theme" class="form-control">
{{theme_options}}
</select>
</div>
<div class="form-group">
<label>{{L_BOARD}}</label>
<select id="dlg_background" class="form-control">
<option value="default">{{L_BOARD_DEFAULT}}</option>
<option value="wood">{{L_BOARD_WOOD}}</option>
</select>
</div>
<div class="form-group">
<label>{{L_SHOW_COORDS}}</label>
<label class="switch">
<input type="checkbox" id="dlg_show_coords" onchange="toggleCoordsOptions()" {{cfg_coord_chk}}>
<span class="slider"></span>
</label>
</div>
<div id="coords_options_panel" class="coords-options" style="display: none;">
<div class="coords-radio">
<label><input type="radio" name="dlg_coords_mode" value="Normal" {{chk_normal}}> {{L_COORDS_OUTSIDE}}</label>
<label><input type="radio" name="dlg_coords_mode" value="Full" {{chk_full}}> {{L_COORDS_FULL}}</label>
</div>
</div>
<!-- Notazione Pezzi (Placeholder) -->
<div class="form-group">
<label>{{L_PIECE_NOTATION}}</label>
<select class="form-control" disabled>
<option>{{L_ICONS}}</option>
</select>
</div>
<div class="form-group">
<label>{{L_TEXT_COLOR}}</label>
<input type="color" id="dlg_text_color" class="form-control" value="{{cfg_text}}" style="height:40px; padding:0;">
</div>
<div class="form-group">
<label>{{L_LANGUAGE}}</label>
<select id="dlg_language" class="form-control">
{{language_options}}
</select>
</div>
</div>
<div class="modal-actions">
<button type="button" class="btn-cancel" onclick="closeSettings()">{{L_CANCEL}}</button>
<button type="button" class="btn-save" onclick="saveSettings()">{{L_SAVE}}</button>
</div>
</form>
</div>
<!-- New Game Modal -->
<div id="newGameModal" class="settings-modal" style="display:none;">
<div class="settings-header" style="height: 60px; background:none; background-color:#3e2723; display:flex; align-items:center; justify-content:center;">
<h3 style="margin:0; color:white;">{{L_NEW_GAME_TITLE}}</h3>
</div>
<div class="settings-content">
<!-- Game Type -->
<div class="form-group">
<label>{{L_GAME_TYPE}}</label>
<div style="display:flex; gap:10px;">
<label><input type="radio" name="ng_mode" value="HvH" checked onchange="toggleNewGameOptions()"> {{L_HUMAN_VS_HUMAN}}</label>
<label><input type="radio" name="ng_mode" value="HvPC" onchange="toggleNewGameOptions()"> {{L_HUMAN_VS_PC}}</label>
</div>
</div>
<!-- Player 1 -->
<div class="form-group">
<label>{{L_PLAYER1}}</label>
<input type="text" id="ng_player1" class="form-control" value="Giocatore 1">
</div>
<!-- Player 2 (HvH) -->
<div class="form-group" id="grp_player2">
<label>{{L_PLAYER2}}</label>
<input type="text" id="ng_player2" class="form-control" value="Giocatore 2">
</div>
<!-- Level (HvPC) -->
<div class="form-group" id="grp_level" style="display:none;">
<label>{{L_AI_LEVEL}}</label>
<select id="ng_level" class="form-control">
<option value="1">1 - {{L_LEVEL_1}}</option>
<option value="2">2 - {{L_LEVEL_2}}</option>
<option value="3">3 - {{L_LEVEL_3}}</option>
<option value="4">4 - {{L_LEVEL_4}}</option>
<option value="5">5 - {{L_LEVEL_5}}</option>
<option value="6">6 - {{L_LEVEL_6}}</option>
<option value="7">7 - {{L_LEVEL_7}}</option>
<option value="8">8 - {{L_LEVEL_8}}</option>
<option value="9">9 - {{L_LEVEL_9}}</option>
</select>
</div>
<!-- Color -->
<div class="form-group">
<label>{{L_YOUR_COLOR}}</label>
<select id="ng_color" class="form-control">
<option value="White">{{L_WHITE}}</option>
<option value="Black">{{L_BLACK}}</option>
</select>
</div>
</div>
<div class="modal-actions">
<button type="button" class="btn-cancel" onclick="closeNewGame()">{{L_CANCEL}}</button>
<button type="button" class="btn-save" onclick="startNewGame()">{{L_START}}</button>
</div>
<!-- Hidden form for new game submission -->
<form id="newGameForm" method="POST" action="chess.prg" style="display:none;">
<input type="hidden" name="action" value="new_game_start">
<input type="hidden" name="game_mode" id="h_ng_mode">
<input type="hidden" name="player1" id="h_ng_player1">
<input type="hidden" name="player2" id="h_ng_player2">
<input type="hidden" name="level" id="h_ng_level">
<input type="hidden" name="my_color" id="h_ng_color">
<!-- Pass current settings too -->
<input type="hidden" name="theme" value="{{cfg_theme}}">
<input type="hidden" name="background" value="{{cfg_bg}}">
<input type="hidden" name="show_coords" value="{{cfg_coord_chk}}">
</form>
</div>
<!-- Save Game Modal -->
<div id="saveGameModal" class="settings-modal" style="display:none;">
<div class="settings-header" style="height: 60px; background:none; background-color:#3e2723; display:flex; align-items:center; justify-content:center;">
<h3 style="margin:0; color:white;">{{L_SAVE_GAME_TITLE}}</h3>
</div>
<div class="settings-content">
<div class="form-group">
<label>{{L_GAME_NAME}}</label>
<input type="text" id="save_name" class="form-control" placeholder="{{L_ENTER_NAME}}">
</div>
</div>
<div class="modal-actions">
<button type="button" class="btn-cancel" onclick="document.getElementById('saveGameModal').style.display='none'">{{L_CANCEL}}</button>
<button type="button" class="btn-save" onclick="submitSaveGame()">{{L_SAVE}}</button>
</div>
</div>
<!-- Load Game Modal -->
<div id="loadGameModal" class="settings-modal" style="display:none;">
<div class="settings-header" style="height: 60px; background:none; background-color:#3e2723; display:flex; align-items:center; justify-content:center;">
<h3 style="margin:0; color:white;">{{L_LOAD_GAME_TITLE}}</h3>
</div>
<div class="settings-content" style="max-height: 300px; overflow-y: auto;">
<ul id="game_list_ul" style="list-style: none; padding: 0; margin: 0;">
{{saved_games_list}}
</ul>
</div>
<div class="modal-actions">
<button type="button" class="btn-cancel" onclick="document.getElementById('loadGameModal').style.display='none'">{{L_CLOSE}}</button>
</div>
</div>
<script>
function selectSquare(squareId) {
document.getElementById('action').value = 'select';
document.getElementById('clicked_square').value = squareId;
document.getElementById('chessForm').submit();
}
function newGame() {
document.getElementById('action').value = 'new';
document.getElementById('chessForm').submit();
}
// Initialize Dialog
function openSettings() {
document.getElementById('settingsModal').style.display = 'block';
// Set Background Select
let bgVal = document.getElementById('h_background_dlg').value || 'default';
document.getElementById('dlg_background').value = bgVal;
toggleCoordsOptions();
}
function closeSettings() {
document.getElementById('settingsModal').style.display = 'none';
}
function toggleCoordsOptions() {
let chk = document.getElementById('dlg_show_coords').checked;
let panel = document.getElementById('coords_options_panel');
panel.style.display = chk ? 'block' : 'none';
}
function saveSettings() {
// Hidden inputs are in settingsForm, IDs end with _dlg
document.getElementById('h_background_dlg').value = document.getElementById('dlg_background').value;
document.getElementById('h_theme_dlg').value = document.getElementById('dlg_theme').value;
document.getElementById('h_text_color_dlg').value = document.getElementById('dlg_text_color').value;
document.getElementById('h_language_dlg').value = document.getElementById('dlg_language').value;
// Checkbox handling logic fixed:
if(document.getElementById('dlg_show_coords').checked) {
document.getElementById('h_show_coords_dlg').name = "show_coords";
document.getElementById('h_show_coords_dlg').value = "1";
} else {
document.getElementById('h_show_coords_dlg').removeAttribute("name");
}
document.getElementById('action_settings').value = 'save_settings';
// Copy Coords Mode radio
let radios = document.getElementsByName('dlg_coords_mode');
for(let r of radios) { if(r.checked) document.getElementById('h_coords_mode_dlg').value = r.value; }
document.getElementById('settingsForm').submit();
}
// New Game Modal Logic
function newGame() {
document.getElementById('newGameModal').style.display = 'block';
toggleNewGameOptions();
}
// AI Auto-Move Logic
var isComputerThinking = "{{computer_thinking}}" === "Y";
if ( isComputerThinking ) {
// Show some indicator?
document.title = "{{L_COMPUTER_THINKING}}";
setTimeout(function() {
document.getElementById('action').value = 'computer_move';
document.getElementById('chessForm').submit();
}, 100); // 100ms delay, fast response
}
function closeNewGame() {
document.getElementById('newGameModal').style.display = 'none';
}
function toggleNewGameOptions() {
let mode = document.querySelector('input[name="ng_mode"]:checked').value;
if(mode === 'HvPC') {
document.getElementById('grp_player2').style.display = 'none';
document.getElementById('grp_level').style.display = 'flex';
} else {
document.getElementById('grp_player2').style.display = 'flex';
document.getElementById('grp_level').style.display = 'none';
}
}
function startNewGame() {
let mode = document.querySelector('input[name="ng_mode"]:checked').value;
document.getElementById('h_ng_mode').value = mode;
document.getElementById('h_ng_player1').value = document.getElementById('ng_player1').value;
if(mode === 'HvPC') {
let level = document.getElementById('ng_level').value;
document.getElementById('h_ng_level').value = level;
document.getElementById('h_ng_player2').value = "Computer (Liv. " + level + ")";
} else {
document.getElementById('h_ng_player2').value = document.getElementById('ng_player2').value;
}
document.getElementById('h_ng_color').value = document.getElementById('ng_color').value;
document.getElementById('newGameForm').submit();
}
function openSaveGame() {
document.getElementById('saveGameModal').style.display = 'block';
}
function submitSaveGame() {
let name = document.getElementById('save_name').value;
if(!name) { alert("{{L_ENTER_NAME_ALERT}}"); return; }
// Use the main form to submit action
document.getElementById('action').value = 'save_game';
// Create a hidden input for save_name dynamically
let input = document.createElement("input");
input.type = "hidden";
input.name = "save_name";
input.value = name;
document.getElementById('chessForm').appendChild(input);
document.getElementById('chessForm').submit();
}
function openLoadGame() {
// Trigger backend to fetch list
document.getElementById('action').value = 'load_game_list';
document.getElementById('chessForm').submit();
}
function loadGame( id ) {
document.getElementById('action').value = 'load_game';
document.getElementById('game_id').value = id;
document.getElementById('chessForm').submit();
}
// Splash Screen Logic
window.onload = function() {
// Check if we are in "new" or "reload".
// Simple logic: if sessionStorage has flag, skip.
// User requested: "at the beginning".
// Check if there was an action (move, select, save, etc.). If so, DO NOT show splash.
var hasAction = "{{has_active_action}}";
if (hasAction === "Y" || sessionStorage.getItem('splashShown')) {
document.getElementById('splash-screen').style.display = 'none';
document.getElementById('main-container').style.display = 'flex';
sessionStorage.setItem('splashShown', 'true');
} else {
setTimeout(function() {
document.getElementById('splash-screen').style.opacity = 0;
document.getElementById('splash-screen').style.transition = "opacity 1s";
setTimeout(function(){
document.getElementById('splash-screen').style.display = 'none';
document.getElementById('main-container').style.display = 'flex';
}, 1000);
sessionStorage.setItem('splashShown', 'true');
}, 1500);
}
// Check if we need to auto-open Load Modal (e.g. after list_games action)
if( "{{open_load_modal}}" === "Y" ) {
openLoadGame();
}
var msg = "{{status_msg}}";
if (msg && msg.indexOf("Saved") !== -1) alert(msg);
};
</script>
</body>
</html>
```style.view
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background: white;
border-radius: 20px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
width: 350px; /* Fixed width for calculator */
}
h1 {
color: #333;
margin-bottom: 20px;
font-size: 24px;
text-align: right;
font-weight: 300;
}
.calculator-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.calc-display {
grid-column: 1 / -1;
background-color: #f8f9fa;
color: #333;
padding: 20px;
border-radius: 10px;
font-size: 2rem;
text-align: right;
border: none;
margin-bottom: 20px;
box-shadow: inset 0 2px 5px rgba(0,0,0,0.05);
}
.calc-btn {
border: none;
background-color: #f0f0f0;
font-size: 1.25rem;
color: #333;
padding: 20px;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
user-select: none;
}
.calc-btn:hover {
background-color: #e0e0e0;
}
.calc-btn.operator {
background-color: #e0e0e0;
color: #333;
font-weight: bold;
font-size: 1.4rem; /* Larger font for operators */
}
.calc-btn.operator:hover {
background-color: #d0d0d0;
}
.calc-btn.equals {
grid-column: span 2;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.calc-btn.equals:hover {
opacity: 0.9;
}
.calc-btn.clear {
background-color: #ffebee;
color: #d32f2f;
}
.calc-btn.clear:hover {
background-color: #ffcdd2;
}
/* Hide actual form elements */
.hidden-form {
display: none;
}
/* Result specific */
.result-box {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
text-align: center;
margin-bottom: 20px;
}
.result-value {
font-size: 36px;
color: #667eea;
font-weight: bold;
margin-top: 10px;
}
</style>create "Mosse" folder here the procedure create a dbf to save all moves
create board folder and insert this png

on board folder create the folder "themes"
on themes folder create "Alpha" and "Balestegui" folders
download from here
https://github.com/FiveTechSoft/FWH_tools/blob/master/themes.rar
Silvio,
Truly impressive. For me, the important thing is that the tool helps everyone create their own applications. With these examples created in just one day, I'm satisfied.
Challenge: Player 2 (AI) Use CURL... :wink:
Congratulations.
C.
Carles wrote:Silvio,
Truly impressive. For me, the important thing is that the tool helps everyone create their own applications. With these examples created in just one day, I'm satisfied.
Challenge: Player 2 (AI) Use CURL... :wink:
Congratulations.
C.
thanks C.