
chess.prg
// Chess Game Logic for HIX
// Uses FEN for state management
function Main()
local oErr, cHtml
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, hParams
local cFEN, cSelected, cTurn
local aValidMoves := {}
local cClicked, cPiece, cTurnColor
if hb_HHasKey( hPost, "action" )
cAction := hPost["action"]
else
cAction := "new"
endif
if hb_HHasKey( hPost, "fen" )
cFEN := hPost["fen"]
else
cFEN := InitBoard()
endif
if Empty( cFEN )
cFEN := InitBoard()
endif
cSelected := ""
if hb_HHasKey( hPost, "selected" )
cSelected := hPost["selected"]
endif
do case
case cAction == "new"
cFEN := InitBoard()
cSelected := ""
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 )
cFEN := MakeMove( cFEN, cSelected, cClicked )
cSelected := "" // Move done, deselect
else
// Invalid move.
// If clicked square has own piece, switch selection.
if IsOwnPiece( cPiece, cTurnColor )
cSelected := cClicked
aValidMoves := GetValidMoves( 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 := GetValidMoves( cFEN, cSelected )
endif
endif
endif
endif
endcase
hParams := {;
"fen" => cFEN, ;
"board_html" => RenderBoard( cFEN, cSelected, aValidMoves ), ;
"turn" => iif( GetTurn(cFEN) == "w", "White", "Black" ), ;
"selected" => cSelected ;
}
cHtml := View( "chess", hParams )
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 IsValidMove( cFEN, cFrom, cTo )
local aMoves := GetValidMoves( cFEN, cFrom )
local i
for i := 1 to Len(aMoves)
if aMoves[i] == cTo
return .T.
endif
next
return .F.
function MakeMove( cFEN, cFrom, cTo )
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 GetValidMoves( 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 )
local aGrid := FENToGrid( cFEN )
local cHtml := '<div class="chess-board">'
local r, c, cSquare, cClass, cContent, cPiece, cColorClass
local lIsSelected, lIsValid
for r := 1 to 8
for c := 1 to 8
cSquare := CoordsToSquare( r, c )
cPiece := aGrid[r, c]
// Background color
if (r + c) % 2 == 0
cClass := "white-square"
else
cClass := "black-square"
endif
// Selection highlight
if cSquare == cSelected
cClass += " selected"
endif
// Piece
if !Empty(cPiece)
cContent := GetPieceSymbol( cPiece )
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>'
return cHtml
function GetPieceSymbol( cPiece )
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:\temp\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
function ValToChar( u )
local cType := ValType( u )
if cType == "C"; return u; endif
if cType == "N"; return AllTrim(Str(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 IsUpperLocal( cChar )
local nAsc := Asc( cChar )
return nAsc >= 65 .and. nAsc <= 90views/chess.view
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hix Chess</title>
{{View("style")}}
<style>
.chess-container {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
.chess-board {
display: grid;
grid-template-columns: repeat(8, 1fr);
width: 480px;
height: 480px;
border: 10px solid #4a3021;
user-select: none;
}
.square {
width: 60px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
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;
color: #333;
}
.turn-indicator {
margin-left: 10px;
padding: 5px 10px;
background: #eee;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="chess-container">
<h1>HIX Chess</h1>
<div class="status-panel">
Turn: <span class="turn-indicator">{{turn}}</span>
</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="">
<!-- Board injected from PRG -->
{{board_html}}
<div class="controls">
<button type="button" class="btn" onclick="newGame()">New Game</button>
</div>
</form>
</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();
}
</script>
</body>
</html>views/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>








