--[[ MINECRAFT CHESS Written by Nitrogen Fingers Version: 0.85 Acknowledgements: ASCII art by David Moeser (1995), Alefith (1995) and Anonyoums (1998) ]]-- version = 0.85 --[[ CHESS ENGINE REGION The code within this region handles the logic of playing the chess game, including the main engine itself. ]]-- local var w,h = term.getSize() --Whether or not the player is white local var youAreWhite = true --If it's white's turn (default is yes) local var whiteTurn = true --Whether or not the player is still in the game interface local playing = true --Whether or not the player is still able to make moves (not checkmate etc.) local gameOver = true --Whether or not the black pieces are visible (flashes on and off) local blackVisible = false --The position of the white king local whiteKing = { x = 5, y = 1 } --The position of the black king local blackKing = { x = 5, y = 8 } --A concatenation of the letters typed during the parsing phase local lastQuery = "" --Whether the game is playing locally or network local isLocal = true --The address of the other player (if there is one) local opponentID = 1 --The list of all monitors local monitorList = {} --The side the monitor is on local side = nil -- Board consists of 2D array of strings -- String format = where sign is + for white, - for black --[[ Pieces: K = KING Q = QUEEN B = BISHOP N = KNIGHT C = CASTLE p = PAWN --]] board = { -- a b c d e f g h [8] = { " ", " ", " ", " ", " ", " ", " ", " " }, [7] = { " ", " ", " ", " ", " ", " ", " ", " " }, [6] = { " ", " ", " ", " ", " ", " ", " ", " " }, [5] = { " ", " ", " ", " ", " ", " ", " ", " " }, [4] = { " ", " ", " ", " ", " ", " ", " ", " " }, [3] = { " ", " ", " ", " ", " ", " ", " ", " " }, [2] = { " ", " ", " ", " ", " ", " ", " ", " " }, [1] = { " ", " ", " ", " ", " ", " ", " ", " " } -- a b c d e f g h } --[[ The following is tracked under movesMade: - player: Which player moved (+ for white, - for black) - piece: What kind of piece moved (p, Q, N, C etc) - oldx, oldy, newx, newy: Coordinates (numbers please) - capture: Whether or not the move resulted in a capture (Bf4xe5 for example) - castle: If the move was a castle - 0 for none, -1 for QS and 1 for KS - promotion: A string. Empty if no promotion, or the piece promoted to - ep: Whether or not the capture was an en passant - check: Whether or not the move resulted in a check (Bf3-g2+ for example) - mate: Whether or not the move resulted in a mate (Bf3-g2# for example) - stale: Whether or not the move resulted in a stalemate (Bf3-g2$ for example) ]]-- movesMade = { } --Prints the player's color and the current turn function printHeader() term.setCursorPos(2, 1) if youAreWhite then term.write("You are WHITE") else term.write("You are BLACK") end term.setCursorPos(28, 1) if whiteTurn then term.write("White to play") else term.write("Black to play") end term.setCursorPos(1, 2) term.write(string.rep("-", w)) end --Prints a list of the last 10 moves made function printMovesMade() local var min = 1 local var max = #movesMade if #movesMade > 9 then min = #movesMade - 9 end for i = min,max do term.setCursorPos(2, 3 + (i - min + 1)) term.clearLine() --We convert the movesMade entry to a string here using long notation- B3exf2+ ect. local entry = convertCoordinateToLetter(movesMade[i].oldx)..movesMade[i].oldy if movesMade[i].castle == 0 then --This is the standard, non-castling notation used if movesMade[i].piece ~= "p" then entry = movesMade[i].piece..entry end if movesMade[i].capture then entry = entry.." x " else entry = entry.." - " end entry = entry..convertCoordinateToLetter(movesMade[i].newx)..movesMade[i].newy if movesMade[i].promotion ~= "" then entry = entry.."="..movesMade[i].promotion end if movesMade[i].ep then entry = entry.."e.p." end if movesMade[i].check then entry = entry.."+" elseif movesMade[i].mate then entry = entry.."#" elseif movesMade[i].stale then entry = entry.."$" end elseif movesMade[i].castle == 1 then entry = "O-O" elseif movesMade[i].castle == -1 then entry = "O-O-O" end term.write(i..": "..string.rep(" ", 3 - #tostring(i))..entry) end end --Prints the board. function printBoard() for i = 1,8 do local idx = i if youAreWhite then idx = 9 - i end term.setCursorPos(25, 3 + i) term.write(idx.." |") for j=1,8 do local jdx = j if not youAreWhite then jdx = 9 - j end local str = board[idx][jdx] if term.isColour() then if string.sub(str, 1, 1)== "-" then term.setTextColour(colours.black) str = string.sub(str, 2) elseif string.sub(str, 1, 1) == "+" then term.setTextColour(colours.white) str = string.sub(str, 2) end if idx%2 == jdx%2 then term.setBackgroundColour(colours.brown) else term.setBackgroundColour(colours.yellow) end term.write(" "..str) term.setTextColour(colours.white) term.setBackgroundColour(colours.black) else if string.sub(str, 1, 1)== "-" and not blackVisible then str = " " end if str ~= " " then str = string.sub(str, 2) end if str == " " and idx%2 == jdx%2 then str = "*" end term.write(" "..str) end end end term.setCursorPos(25, 12) term.write(" "..string.rep("-", 16)) term.setCursorPos(25, 13) if youAreWhite then term.write(" a b c d e f g h") else term.write(" h g f e d c b a") end end --This method determines whether a specific move made by a player is legal --If not an error will have details (so these methods are long) --Note we use AmWhite here to test for checking function isLegal(oldx,oldy,newx,newy,amWhite) if not coordinatesSensible(oldx, oldy, newx, newy) then return false end local piece = string.sub(board[oldy][oldx], 2) local isLegal = false if piece=="p" then isLegal = isLegalPawn(oldx,oldy,newx,newy,amWhite,false,false) elseif piece=="C" then isLegal = isLegalCastle(oldx,oldy,newx,newy,amWhite,false) elseif piece=="N" then isLegal = isLegalKnight(oldx,oldy,newx,newy,amWhite,false) elseif piece=="B" then isLegal = isLegalBishop(oldx,oldy,newx,newy,amWhite,false) elseif piece=="Q" then isLegal = isLegalQueen(oldx,oldy,newx,newy,amWhite,false) elseif piece=="K" then isLegal = isLegalKing(oldx,oldy,newx,newy,amWhite,false,false) end if isLegal then if amWhite then if piece=="K" then isLegal = not isInCheck(newx,newy,amWhite) else local oldsrc = board[oldy][oldx] local olddest = board[newy][newx] -- I'm a bit worried about this... test! board[newy][newx] = board[oldy][oldx] board[oldy][oldx] = " " isLegal = not isInCheck(whiteKing.x,whiteKing.y,amWhite) board[oldy][oldx] = oldsrc board[newy][newx] = olddest end else if piece=="K" then isLegal = not isInCheck(newx,newy,amWhite) else local oldsrc = board[oldy][oldx] local olddest = board[newy][newx] -- I'm a bit worried about this... test! board[newy][newx] = board[oldy][oldx] board[oldy][oldx] = " " isLegal = not isInCheck(blackKing.x,blackKing.y,amWhite) board[oldy][oldx] = oldsrc board[newy][newx] = olddest end end if not isLegal then printMessage("That move leaves your king in check.") end end return isLegal end --This loops through every position on the board, and where enemy pieces are --found, determines if any place the king in check. function isInCheck(posx,posy,amWhite) local takeable = "+" if amWhite then takeable = "-" end for i=1,8 do for j=1,8 do if string.sub(board[j][i], 1, 1) == takeable then local piece = string.sub(board[j][i], 2) local legal = false if piece=="p" then legal = isLegalPawn(i,j,posx,posy,not amWhite,true,true) elseif piece=="C" then legal = isLegalCastle(i,j,posx,posy,not amWhite,true) elseif piece=="N" then legal = isLegalKnight(i,j,posx,posy,not amWhite,true) elseif piece=="B" then legal = isLegalBishop(i,j,posx,posy,not amWhite,true) elseif piece=="Q" then legal = isLegalQueen(i,j,posx,posy,not amWhite,true) elseif piece=="K" then legal = isLegalKing(i,j,posx,posy,not amWhite,true,true) end if legal then return true end end end end return false end --Legal for a pawn. Color dependent. Manages movement and capturing (including en passant) --The forking exception means the pawn will ONLY accept diagonals, not forwards function isLegalPawn(oldx,oldy,newx,newy,amWhite,suppress,forKing) if amWhite then local takeable = "-" if newy-oldy == 1 and newx-oldx == 0 and board[newy][newx] == " " and not forKing then return true elseif newy-oldy == 2 and newx-oldx == 0 and board[newy][newx] == " " and board[newy-1][newx] == " " and oldy == 2 and not forKing then return true elseif newy-oldy == 1 and math.abs(newx-oldx) == 1 then if string.sub(board[newy][newx], 1, 1) == takeable or forKing then return true elseif board[newy-1][newx] == takeable.."p" and movesMade[#movesMade].newy == newy-1 and movesMade[#movesMade].newx == newx and math.abs(movesMade[#movesMade].newy - movesMade[#movesMade].oldy) == 2 then return true end if not suppress then printMessage("You cannot take that space with the pawn.") end end if not suppress then printMessage("Your pawn can't move there.") end else local takeable = "+" if newy-oldy == -1 and newx-oldx == 0 and board[newy][newx] == " " and not forKing then return true elseif newy-oldy == -2 and newx-oldx == 0 and board[newy][newx] == " " and board[newy+1][newx] == " " and oldy == 7 and not forKing then return true elseif newy-oldy == -1 and math.abs(newx-oldx) == 1 then if string.sub(board[newy][newx], 1, 1) == takeable or forKing then return true elseif board[newy+1][newx] == takeable.."p" and math.abs(movesMade[#modesMade].newy - movesMade[#movesMade].oldy) == 2 then return true end if not suppress then printMessage("You cannot take that space with the pawn.") end end if not suppress then printMessage("Your pawn can't move there.") end end return false end --Legal for a castle. Manages movement and capturing function isLegalCastle(oldx,oldy,newx,newy,amWhite,suppress) local takeable = "+" if amWhite then takeable = "-" end if newy == oldy then local inc = 1 if newx < oldx then inc = -1 end for i = oldx+inc, newx-inc, inc do if board[newy][i] ~= " " then if not suppress then printMessage("The castle's path is blocked to that space.") end return false end end piece = board[newy][newx] if piece == " " or string.sub(piece, 1, 1) == takeable then return true end if not suppress then printMessage("Your castle can't take that piece.") end return false elseif newx == oldx then local inc = 1 if newy < oldy then inc = -1 end for i = oldy+inc, newy-inc, inc do if board[i][newx] ~= " " then if not suppress then printMessage("The castle's path is blocked to that space.") end return false end end piece = board[newy][newx] if piece == " " or string.sub(piece, 1, 1) == takeable then return true end if not suppress then printMessage("Your castle can't take that piece.") end return false end if not suppress then printMessage(oldx..","..oldy.." can only move along ranks or files.") end return false end --Legal for a knight. Manages movement and capturing function isLegalKnight(oldx,oldy,newx,newy,amWhite,suppress) local takeable = "+" if amWhite then takeable = "-" end if (math.abs(oldx-newx) == 1 and math.abs(oldy-newy) == 2) or (math.abs(oldx-newx) == 2 and math.abs(oldy-newy) == 1) then if board[newy][newx] == " " or string.sub(board[newy][newx], 1, 1) == takeable then return true end end if not suppress then printMessage("Your knight can't move there.") end return false end --Legal for a bishop. Manages movement and capturing function isLegalBishop(oldx,oldy,newx,newy,amWhite,suppress) local takeable = "+" if amWhite then takeable = "-" end if math.abs(oldx-newx) == math.abs(oldy-newy) then local xmp = 1 local ymp = 1 if newx < oldx then xmp = -1 end if newy < oldy then ymp = -1 end for i = 1,math.abs(oldx-newx) - 1 do if board[oldy + (i * ymp)][oldx + (i * xmp)] ~= " " then printMessage("The bishop's path is blocked to that space.") return false end end piece = board[newy][newx] if piece == " " or string.sub(piece, 1, 1) == takeable then return true end printMessage("Your bishop can't take that piece.") return false end if not suppress then printMessage("Bishops can only move along diagonals.") end return false end --Legal for a queen. Manages movement and capturing function isLegalQueen(oldx,oldy,newx,newy,amWhite,suppress) local possible = isLegalBishop(oldx,oldy,newx,newy,amWhite,suppress) or isLegalCastle(oldx,oldy,newx,newy,amWhite,suppress) if not possible and not suppress then printMessage("Your queen can't move there.") end return possible end --Legal for a king. Managed movement and capturing -- The forking exception excludes the ability to castle function isLegalKing(oldx,oldy,newx,newy,amWhite,suppress,forKing) local takeable = "+" if amWhite then takeable = "-" end if newx - oldx == 2 and newy-oldy == 0 and not forKing then return isLegalCastling("king", amWhite) elseif newx - oldx == -2 and newy-oldy == 0 and not forKing then return isLegalCastling("queen", amWhite) elseif math.abs(oldx-newx) > 1 or math.abs(oldy-newy) > 1 then if not suppress then printMessage("Your king can only move one tile in any direction") end return false end piece = board[newy][newx] if piece == " " or string.sub(piece, 1, 1) == takeable then return true end if not suppress then printMessage("Your king can't take that piece.") end return false end --This isLegal method is aimed specifically at castling, with the string being --passed "king" or "queen" to indicate kingside or queenside castling. function isLegalCastling(side,amWhite) if amWhite then if side == "king" then if board[1][6]~= " " or board[1][7] ~= " " then printMessage("You cannot castle- the path is blocked") return false end for i=1,#movesMade do local move = movesMade[i] if move.piece == "K" and move.player == "+" then printMessage("You cannot castle- your king has moved before.") return false elseif move.piece == "C" and move.player == "+" and move.oldx == 8 and move.oldy == 1 then printMessage("You cannot castle- your ks castle has moved before.") return false end end return true elseif side == "queen" then if board[1][2] ~= " " or board[1][3] ~= " " or board[1][4] ~= " " then printMessage("You cannot castle- the path is blocked") return false end for i=1,#movesMade do local move = movesMade[i] if move.piece == "K" and move.player == "+" then printMessage("You cannot castle- your king has moved before.") return false elseif move.piece == "C" and move.player == "+" and move.oldx == 1 and move.oldy == 1 then printMessage("You cannot castle- your qs castle has moved before.") return false end end return true end else if side == "king" then if board[8][6]~= " " or board[8][7] ~= " " then printMessage("You cannot castle- the path is blocked") return false end for i=1,#movesMade do local move = movesMade[i] if move.piece == "K" and move.player == "-" then printMessage("You cannot castle- your king has moved before.") return false elseif move.piece == "C" and move.player == "-" and move.oldx == 8 and move.oldy == 8 then printMessage("You cannot castle- your ks castle has moved before.") return false end end return true elseif side == "queen" then if board[8][2] ~= " " or board[8][3] ~= " " or board[8][4] ~= " " then printMessage("You cannot castle- the path is blocked") return false end for i=1,#movesMade do local move = movesMade[i] if move.piece == "K" and move.player == "-" then printMessage("You cannot castle- your king has moved before.") return false elseif move.piece == "C" and move.player == "-" and move.oldx == 1 and move.oldy == 8 then printMessage("You cannot castle- your qs castle has moved before.") return false end end return true end end end --Submethod of isLegal, determines whether or not the choice of piece and --destination square make sense within the game function coordinatesSensible(oldx, oldy, newx, newy) if oldx == newx and oldy == newy then printMessage("Each piece must move at least 1 square.") return false end if oldx < 1 or oldx > 8 or newx < 1 or newx > 8 or oldy < 1 or oldy > 8 or newy < 1 or newy > 8 then printMessage("The coordinates you provided were invalid") return false end local piece = board[oldy][oldx] if piece == " " then printMessage("There isn't a piece on that space") return false elseif (string.sub(piece, 1, 1) == "+") ~= youAreWhite then printMessage("There's an enemy piece on that space") return false end return true end --This actually acts upon the move made. It moves the piece on the board, adds --an entry to the moves list, tells the other commputer the move is made and --refreshes everything. function makeMove(oldx,oldy,newx,newy,amWhite) local player = "+" local foe = "-" if not amWhite then player = "-" foe = "+" end local capture = false local ep = false if board[newy][newx] ~= " " then capture = true end if board[newy][newx] == " " and math.abs(oldx-newx) == 1 and board[oldy][oldx]:sub(2) == "p" and board[oldy][newx]:sub(2) == "p" then capture = true ep = true board[oldy][newx] = " " end board[newy][newx] = board[oldy][oldx] board[oldy][oldx] = " " local piece = string.sub(board[newy][newx], 2) --We handle updating king positions and CASTLING here castle = 0 if board[newy][newx] == "+K" then whiteKing = { x = newx, y = newy } if newx - oldx == 2 then board[1][6] = board[1][8] board[1][8] = " " castle = 1 elseif newx - oldx == -2 then board[1][4] = board[1][1] board[1][1] = " " castle = -1 end end if board[newy][newx] == "-K" then blackKing = { x = newx, y = newy } if newx - oldx == 2 then board[8][6] = board[8][8] board[8][8] = " " castle = 1 elseif newx - oldx == -2 then board[8][4] = board[8][1] board[8][1] = " " castle = -1 end end --Promotion is handled here promotion = "" if piece == "p" then if (youAreWhite and player == "+" and newy == 8) or (not youAreWhite and player == "-" and newy == 1) then handlePawnPromotion(newx, newy, youAreWhite) promotion = string.sub(board[newy][newx], 2) if not isLocal then rednet.send(opponentID, "#P"..newx..newy..board[newy][newx]) sendToMonitors("#P"..newx..newy..board[newy][newx]) end end end local check = false --Again, needs to be debugged if not amWhite then check = isInCheck(whiteKing.x, whiteKing.y, true) else check = isInCheck(blackKing.x, blackKing.y, false) end table.insert(movesMade, { player = player, piece = piece, oldx = oldx, oldy = oldy, newx = newx, newy = newy, capture = capture, castle = castle, promotion = promotion, ep = ep, check = check, mate = false, stale = false }) end --A specialized function that reads input to determine a pawn promotion --This is called in makeMove, but only if it's the same player function handlePawnPromotion(x,y,amWhite) printMessage("Promote: (Q)ueen, (C)astle, k(N)ight or (B)ishop") term.setCursorPos(1, 16) term.clearLine() term.write("> ") local ptype = "+" if not amWhite then ptype = "-" end local id,key = os.pullEvent("char") if string.upper(key) == "C" then board[y][x] = ptype.."C" elseif string.upper(key) == "N" then board[y][x] = ptype.."N" elseif string.upper(key) == "B" then board[y][x] = ptype.."B" else --We do this by default... easier this way board[y][x] = ptype.."Q" end printMessage("Pawn promoted") --Restarts that timer we so rudely interrupted os.startTimer(0.1) end --This promotes a pawn from a network message function handleRemotePawnPromotion(msg) local x = tonumber(string.sub(msg, 1, 1)) local y = tonumber(string.sub(msg, 2, 2)) local piece = string.sub(msg, 3) board[y][x] = piece movesMade[#movesMade].promotion = string.sub(msg, 4) end --Takes in an input string and converts it to a chess query. --The input method is EXTREMELY strict. This ensures it conforms --The input string must be 5 characters long- the first two are the X and Y of --the piece to move, followed by a space character and the second two coordinates. --Example of a legal input: e5-b7 a1 a6 f3xg2 --Example of illegal input: 55 b3 a4 h8 f3xg2+ --This will return true if the move occurred, false otherwise function parseInput(input,amWhite) if input == "quit" or input == "exit" then if not isLocal then rednet.send(opponentID, "#E") end playing = false return true end if (input == "reset" or input == "restart") and isLocal then resetGame(true) return true end if input == "draw" then printMessage("Waiting for response...") rednet.send(opponentID, "#D") while true do local id,key,msg = os.pullEvent("rednet_message") if msg == "#Y" then printMessage("Game result: 1/2 - 1/2") os.pullEvent("key") playing = false return true elseif msg == "#N" then printMessage("Draw rejected.") os.startTimer(0.2) return false end end end if gameOver then printMessage("Game is over- you can \'exit\' or \'restart\'.") return false end local oldx = convertLetterToCoordinate(string.sub(input, 1, 1)) local oldy = tonumber(string.sub(input, 2, 2)) if not oldy then oldy = -1 end local newx = convertLetterToCoordinate(string.sub(input, 4, 4)) local newy = tonumber(string.sub(input, 5, 5)) if not newy then newy = -1 end if isLegal(oldx,oldy,newx,newy,amWhite) then printMessage("Making move...") makeMove(oldx,oldy,newx,newy,amWhite) return true end return false end --Runs on the OS timer and uses it to update the black pieces flashing on and off function updateBlackFlashing() blackVisible = not blackVisible printBoard() local length = 0.2 if not blackVisible then length = 0.15 end os.startTimer(length) end --This prints the oneline message above the command prompt function printMessage(message) term.setCursorPos(1, 15) term.clearLine() term.write(message) end --This converts a letter coordinate like a or f to a number function convertLetterToCoordinate(input) if input=="a" then return 1 elseif input=="b" then return 2 elseif input=="c" then return 3 elseif input=="d" then return 4 elseif input=="e" then return 5 elseif input=="f" then return 6 elseif input=="g" then return 7 elseif input=="h" then return 8 else return -1 end end --The reverse operation. Converts a number to a letter coordiante. function convertCoordinateToLetter(input) if input==1 then return "a" elseif input==2 then return "b" elseif input==3 then return "c" elseif input==4 then return "d" elseif input==5 then return "e" elseif input==6 then return "f" elseif input==7 then return "g" elseif input==8 then return "h" else return -1 end end --An update function for the input display- displaying the latest string function updateInputDisplay() term.setCursorPos(1, 16) term.clearLine() term.write("> "..lastQuery) end --Restores the board to it's original state, and clears all moves. function resetGame(amWhite) term.clear() youAreWhite = amWhite whiteTurn = true movesMade = {} gameOver = false --[[ board = { -- a b c d e f g h [8] = { " ", " ", " ", " ", " ", " ", " ", " " }, [7] = { " ", " ", " ", " ", "+p", " ", " ", " " }, [6] = { " ", " ", " ", " ", " ", " ", " ", " " }, [5] = { " ", " ", " ", " ", " ", " ", " ", " " }, [4] = { " ", " ", " ", " ", " ", " ", " ", " " }, [3] = { " ", " ", " ", " ", " ", " ", " ", " " }, [2] = { " ", " ", " ", " ", "-p", " ", " ", " " }, [1] = { " ", " ", " ", " ", " ", " ", " ", " " } -- a b c d e f g h }]]-- board = { -- a b c d e f g h [8] = { "-C", "-N", "-B", "-Q", "-K", "-B", "-N", "-C"}, [7] = { "-p", "-p", "-p", "-p", "-p", "-p", "-p", "-p"}, [6] = { " ", " ", " ", " ", " ", " ", " ", " " }, [5] = { " ", " ", " ", " ", " ", " ", " ", " " }, [4] = { " ", " ", " ", " ", " ", " ", " ", " " }, [3] = { " ", " ", " ", " ", " ", " ", " ", " " }, [2] = { "+p", "+p", "+p", "+p", "+p", "+p", "+p", "+p"}, [1] = { "+C", "+N", "+B", "+Q", "+K", "+B", "+N", "+C"} -- a b c d e f g h } end --This manages the game logic. It runs in an infinite loop (of course) function playGame() term.clear() os.startTimer(0.2) while playing do printMovesMade() printBoard() printHeader() term.setCursorPos(1, 16) term.clearLine() term.write("> ") if not isLocal and youAreWhite ~= whiteTurn then local proMsg = nil while true do local id, key, msg = os.pullEvent() if id == "timer" then updateBlackFlashing() elseif id == "rednet_message" and key == opponentID then if msg == "#E" then playing = false break elseif msg == "#D" then printMessage("Draw offered- accept? (Y)es or (N)o") local id,key = os.pullEvent("char") if key == "Y" or key == "y" then playing = false printMessage("Game result: 1/2 - 1/2") rednet.send(opponentID, "#Y") os.pullEvent("key") break else printMessage("Draw rejected.") rednet.send(opponentID, "#N") os.startTimer(0.2) end elseif string.find(msg, "#P") == 1 then proMsg = string.gsub(msg, "#P", "") else local oldx = convertLetterToCoordinate(string.sub(msg, 1, 1)) local oldy = tonumber(string.sub(msg, 2, 2)) local newx = convertLetterToCoordinate(string.sub(msg, 4, 4)) local newy = tonumber(string.sub(msg, 5, 5)) makeMove(oldx,oldy,newx,newy,not youAreWhite) if proMsg then handleRemotePawnPromotion(proMsg) end break end end end else local noMoves = #movesMade while noMoves == #movesMade do local id,key = os.pullEvent() if id == "timer" then updateBlackFlashing() elseif id == "key" then if key == 14 then if #lastQuery > 1 then lastQuery = string.sub(lastQuery, 1, #lastQuery-1) else lastQuery = "" end elseif key == 28 then local occ = parseInput(lastQuery, youAreWhite) if occ and not isLocal then rednet.send(opponentID, lastQuery) sendToMonitors(lastQuery) end if (lastQuery == "reset" or lastQuery == "restart") then lastQuery = "" break end lastQuery = "" if not playing then break end end updateInputDisplay() elseif id == "char" then lastQuery = lastQuery..key updateInputDisplay() end end end if isLocal then youAreWhite = not youAreWhite end whiteTurn = not whiteTurn end --With the menu system, we allow the player to return to the main menu --This can be turned off to make an exit quit chess altogether playing = true sendToMonitors("#Q") end --Finds the side the modem is on, and opens it function openModem() for i=1,#rs.getSides() do if peripheral.isPresent(rs.getSides()[i]) and peripheral.getType(rs.getSides()[i]) == "modem" then side = rs.getSides()[i] rednet.open(side) return true end end return false end --Sends a message to each monitor hooked up to the network function sendToMonitors(msg) for k,v in pairs(monitorList) do rednet.send(v, msg) end end --[[ GUI REGION This code handles the visual interface for the menu system and network play of the Chess system ]]-- --The error message displayed when a network error has occurred local networkErrorMessage = "" --The currently selected menu option local menuSel = 1 --Which help page is currently being viewed local helpPage = 1 --Draws the main menu function drawMainInterface() term.clear() drawASCIIKing(w-12,h-14) drawASCIIRook(5, h-9) term.setCursorPos(4, 3) term.write("Minecraft Chess") term.setCursorPos(4, 4) term.write("===============") printCentered("Local Game", 7) printCentered("Network Game", 10) printCentered("Instructions", 13) printCentered("Quit Game", 16) printCentered("------------", 5 + menuSel * 3) end --Draws the "host network game" menu interface function drawNetworkInterface() term.clear() drawASCIIBishop(w - 10, h - 12) drawASCIIPawn(3, h - 7) drawASCIIPawn(5, h - 6) printCentered("Network Game", 2) printCentered("============", 3) printCentered("Host", 7) printCentered("Join", 10) printCentered("Back", 13) printCentered("------", 5 + menuSel * 3) end --Draws the screen that requests an computer ID function drawIDRequest() term.clear() drawASCIIBishop(w - 10, h - 12) drawASCIIPawn(3, h - 7) drawASCIIPawn(5, h - 6) term.setCursorPos(4, 5) term.write("Enter the host's computer ID: ") end --In the event players can't connet, by timeout or no modem or something this says so function drawNetworkError() term.clear() drawASCIIBishop(w - 10, h - 12) drawASCIIPawn(3, h - 7) drawASCIIPawn(5, h - 6) printCentered(networkErrorMessage, 5) printCentered("Press any key to go back.", 10) end --Draws the "wait for connection" interface function drawWaitForConnection() term.clear() drawASCIIBishop(w - 10, h - 12) drawASCIIPawn(3, h - 7) drawASCIIPawn(5, h - 6) printCentered("Waiting for connection...", 5) printCentered("(Your ID is "..os.getComputerID()..")", 6) printCentered("Press enter to cancel", 8) end --Draws the tutorial help functions function drawHelp() term.clear() drawASCIIKnight(3, h - 7) drawASCIIPawn(13, h - 6) local nextstr = "Next" local prevstr = "Prev" local offset = 0 for i=1,#helpText[helpPage] do term.setCursorPos(2,1 + i) term.write(helpText[helpPage][i]) end if helpPage == 1 then prevstr = string.rep(" ", 4) offset = 12 end if helpPage == #helpText then nextstr = string.rep(" ", 4) end term.setCursorPos(20, 15) term.write(prevstr..string.rep(" ", 8).."Back"..string.rep(" ", 8)..nextstr) term.setCursorPos(8 + offset + menuSel * 12, 16) term.write("----") term.setCursorPos(35, 18) term.write("Page "..helpPage.." of "..#helpText) end --Starts a local game function startLocalGame() resetGame(true) isLocal = true playGame() currentInterface = interfaces["main"] end --Starts a network game function startNetworkGame(amWhite) resetGame(amWhite) isLocal = false playGame() currentInterface = interfaces["main"] end --[[The following methods are tiny stubs to fix the variable no access issue]]-- --Necessary to set the interface back to main(no access from inside a list) function returnToMainMenu() currentInterface = interfaces["main"] end --Necessary to update the help pages function updateHelpPage(value) helpPage = value if helpPage ~= 1 then menuSel = 2 end end --Prints the provided string in the centre of the screen at the y coordinate specified function printCentered(str,y) term.setCursorPos(w/2 - #str/2, y) term.write(str) end --Draws an ASCII king at the desired coordinates. Dimensions are (10,14) characters function drawASCIIKing(x,y) local kingList = { " _||_ ", " _||_ ", " _/____\\_ ", " \\ / ", " \\____/ ", " (____) ", " | | ", " | | ", " | | ", " | | ", " |__| ", " / \\ ", " (______) ", "(________)", } for i=1,#kingList do term.setCursorPos(x, y + i - 1) term.write(kingList[i]) end end --Draws an ASCII rook at the desired coordinates. Dimensions are (8,9) characters function drawASCIIRook(x,y) local rookList = { " |'-'-'|", " \\ / ", " | | ", " | | ", " | | ", " | | ", " |___| ", " /_____\\ ", "(_______)" } for i=1,#rookList do term.setCursorPos(x, y + i - 1) term.write(rookList[i]) end end --Draws an ASCII pawn at the desired coordinates. Dimensions are (6,6) characters function drawASCIIPawn(x,y) local pawnList = { " __ ", " ( ) ", " || ", " || ", " /__\\ ", "(____)" } for i=1,#pawnList do term.setCursorPos(x, y + i - 1) term.write(pawnList[i]) end end --Draws an ASCII knight at the desired coordinates. Dimensions are (7,9) characters function drawASCIIKnight(x,y) local knightList = { " __/\"\"\"\\ ", "]___ O }", " / }", " /~ }", " \\____/ ", " /____\\ ", " (______)" } for i=1,#knightList do term.setCursorPos(x, y + i - 1) term.write(knightList[i]) end end --Draws an ASCII bishop at the desired coordinates. Dimensions are (8,11) characters function drawASCIIBishop(x,y) local bishopList = { " _<> ", " /\\\\ \\", " \\ \\) /", " \\__/ ", " (____) ", " | | ", " | | ", " | | ", " |__| ", " /____\\ ", "(______)" } for i=1,#bishopList do term.setCursorPos(x, y + i - 1) term.write(bishopList[i]) end end --Requests an ID for the enemy computer function inputOpponentID() opponentID = tonumber(io.read()) if not opponentID then networkErrorMessage = "The ID you entered is invalid" currentInterface = interfaces["networkerror"] else currentInterface = interfaces["join"] end end --[WOLVAN, your code goes here]-- --Attempts to join a hosted game function waitForHost() currentInterface = interfaces["main"] if not openModem() then networkErrorMessage = "Your computer doesn't have a modem!" currentInterface = interfaces["networkerror"] return end rednet.send(opponentID, "#C") while true do local id,msg = rednet.receive(5) currentInterface = interfaces["main"] if id == opponentID and msg == "#C" then currentInterface = interfaces["waittostart"] return end if id == nil then break end end networkErrorMessage = "The connection timed out." currentInterface = interfaces["networkerror"] end --Waits on a user to join the game function waitForClient() if not openModem() then networkErrorMessage = "Your computer doesn't have a modem!" currentInterface = interfaces["networkerror"] return end while true do local evt,id,msg = os.pullEvent() if evt == "rednet_message" and msg == "#C" then opponentID = id rednet.send(opponentID, "#C") currentInterface = interfaces["addmonitors"] return elseif evt == "key" and id == 28 then currentInterface = interfaces["main"] return end end networkErrorMessage = "The connection timed out." currentInterface = interfaces["networkerror"] end --Draws the request to add more monitors or just move on function drawAddMonitors() term.clear() drawASCIIBishop(w - 10, h - 12) drawASCIIPawn(3, h - 7) drawASCIIPawn(5, h - 6) printCentered("Connection established- add monitors now", 5) printCentered("(Your ID is "..os.getComputerID()..")", 6) printCentered("Press any key to start the game", 8) end --Waits for any monitors to connect to the network, and starts a new game when ready function addMonitors() while true do local id,key,value = os.pullEvent() if id == "rednet_message" and value=="#M" then table.insert(monitorList, key) rednet.send(opponentID, "#M"..key) rednet.send(key, "#Y") elseif id == "key" and key == 28 then rednet.send(opponentID, "#S") startNetworkGame(true) currentInterface = interfaces["main"] return end end end --Displays a simple "waiting to start" message function drawWaitingToStart() term.clear() drawASCIIBishop(w - 10, h - 12) drawASCIIPawn(3, h - 7) drawASCIIPawn(5, h - 6) printCentered("Connection Established!", 5) printCentered("Waiting for host...", 6) printCentered("The game will start shortly", 8) end --Listens for any monitors that may be received and when #S appears the game starts function waitToStart() while true do local id, key, value = os.pullEvent("rednet_message") if key == opponentID then if string.find(value, "#M") == 1 then value = string.sub(value, 3) if tonumber(value) then table.insert(monitorList, (tonumber(value))) end elseif value == "#S" then startNetworkGame(false) currentInterface = interfaces["main"] return end end end end --[[ Construction of an interface menu has the following requirements: the index should be the string identifying that menu element options - A list of all menu options in order of selection draw - a function that draws the menu setup - a function that organizes the setup (this may include starting a new game) nextKeyCode - the key the user must strike to move to the next menu option prevKeyCode - the key the user must strike to move to the previous menu option input - overrides the standard key handler, if the interface has one. ]]-- interfaces = { ["main"] = { options = {"local", "network", "page1", "quit"}, draw = drawMainInterface, nextKeyCode = 208, prevKeyCode = 200 }, ["local"] = { setup = startLocalGame }, ["network"] = { options = {"host", "hostidinput", "main"}, draw = drawNetworkInterface, nextKeyCode = 208, prevKeyCode = 200 }, ["host"] = { draw = drawWaitForConnection, input = waitForClient }, ["hostidinput"] = { draw = drawIDRequest, input = inputOpponentID }, ["addmonitors"] = { draw = drawAddMonitors, input = addMonitors }, ["waittostart"] = { draw = drawWaitingToStart, input = waitToStart }, ["join"] = { draw = drawWaitForConnection, input = waitForHost }, ["networkerror"] = { draw = drawNetworkError, input = function() os.pullEvent("key") returnToMainMenu() end }, ["unimplemented"] = { draw = drawUnimplementedInterface, input = function() os.pullEvent("key") returnToMainMenu() end }, ["timeout"] = { draw = drawTimeout }, --The help pages ["page1"] = { setup = function() updateHelpPage(1) end, options = {"main", "page2"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page2"] = { setup = function() updateHelpPage(2) end, options = {"page1", "main", "page3"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page3"] = { setup = function() updateHelpPage(3) end, options = {"page2", "main", "page4"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page4"] = { setup = function() updateHelpPage(4) end, options = {"page3", "main", "page5"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page5"] = { setup = function() updateHelpPage(5) end, options = {"page4", "main", "page6"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page6"] = { setup = function() updateHelpPage(6) end, options = {"page5", "main", "page7"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page7"] = { setup = function() updateHelpPage(7) end, options = {"page6", "main", "page8"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page8"] = { setup = function() updateHelpPage(8) end, options = {"page7", "main", "page9"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page9"] = { setup = function() updateHelpPage(9) end, options = {"page8", "main", "page10"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page10"] = { setup = function() updateHelpPage(10) end, options = {"page9", "main", "page11"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page11"] = { setup = function() updateHelpPage(11) end, options = {"page10", "main", "page12"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page12"] = { setup = function() updateHelpPage(12) end, options = {"page11", "main", "page13"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["page13"] = { setup = function() updateHelpPage(13) end, options = {"page12", "main"}, draw = drawHelp, nextKeyCode = 205, prevKeyCode = 203 }, ["quit"] = { draw = function() print("Do nothing") end, setup = function () if side then rednet.close(side) end playing = false term.clear() term.setCursorPos(1,1) end } } helpText = { [1] = { --E" "Moving on the board:", "", "Chess is taken in turns between black and white, with", "white always starting first. To move your piece, enter", "the desired space with the following notation:", "", " -" }, [2] = { "Example 1:", "The following move :", "", " 4 | * ", " 3 | * * ", " 2 | p p p ", " 1 | Q K B ", " -------", " > e2-e4 ... d e f " }, [3] = { "Example 1:", "Will move white's pawn forward 2 squares.", "", " 1. e2 - e4 4 | p ", " 3 | * * ", " 2 | p * p ", " 1 | Q K B ", " -------", " > e2-e4 ... d e f " }, [4] = { "Moving on the board (CONT):", "", "Unlike standard chess notation, you do not need", "to specify the piece that is being moved.", "Taking pieces works in the same way as moving,", "with a few exceptions." }, [5] = { "En Passant:", "", "In taking a pawn en passant, the game assumes the", "pawn has only moved one square- hence the player", "should move his pawn as though it is on the", "diagonal square rather than the horizontal." }, [6] = { "Example 2:", "The following move (c5 is a white pawn):", "", " ... 7 | p * K * p ", " 11. c4 - c5 6 | * * * ", " 12. d7 - d5 5 | p p * ", " 4 | * * Q ", " -----------", " > c5-d6 ... c d e f g " }, [7] = { "Example 2:", "Will capture d5 en passant and check e7.", "", " ... 7 | p K * p ", " 11. c4 - c5 6 | * p * * ", " 12. d7 - d5 5 | * * ", " 13. c5 x d6 e.p.+ 4 | * * Q ", " -----------", " > c5-d6 ... c d e f g " }, [8] = { "Castling:", "", "To castle your king, simply move him to the left", "two spaces (to castle queenside), or two spaces", "right (to castle kingside). This will move your", "castle automatically." }, [9] = { "Example 3:", "The following move:", "", " ... 4 | * * ", " 7. Bc1 - a3 3 | B p N * ", " 8. d5 - d7 2 | p * p p ", " 1 | C * K ", " -----------", " > e1-c1 ... a b c d e " }, [10] = { "Example 3:", "Will castle white queenside.", "", " ... 4 | * * ", " 7. Bc1 - a3 3 | B p N * ", " 8. d5 - d7 2 | p * p p ", " 9. O-O-O 1 | * K C * ", " -----------", " > e1-c1 ... a b c d e " }, [11] = { "Other commands:", "", "exit: will leave the current game", "reset: starts a fresh game (local only)", "draw: offer your opponent a draw (network only)" }, [12] = { "Monitor Display:", "", "For network play, hook a computer up to a 6x6", "monitor and run monitorchess.lua. You can add", "the monitor just before starting, for a bigscreen", "display!" }, [13] = { "Acknowledgements:", "", "Written by Nitrogen Fingers", "Art: Moeser(1995), Alefith (1995) and NN(1998)", "Rednet Support: Wolvan" } } --The board, for tutorials --[[ "", " 7 | * * ", " 6 | * * * ", " 5 | * * ", " 4 | * * * ", " -----------", " > e2-e4 ... c d e f g " ]]-- --The currently active interface currentInterface = interfaces["main"] --The core GUI logic- this runs the main menu and has a default method for passing --through the interface. It also runs setup and draw methods of each interface. function runMenu() --currentInterface = interfaces["main"] while playing do currentInterface.draw() if currentInterface.input then currentInterface.input() else local id,key = os.pullEvent("key") if key == currentInterface.prevKeyCode and menuSel > 1 then menuSel = menuSel - 1 elseif key == currentInterface.nextKeyCode and menuSel < #currentInterface.options then menuSel = menuSel + 1 elseif key == 28 then local setupname = currentInterface.options[menuSel] currentInterface = interfaces[currentInterface.options[menuSel]] menuSel = 1 if currentInterface.setup then currentInterface.setup() end end end end end gameOver = true runMenu() --gameOver = false --playGame()