Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[ 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 = <sign><piece> 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:",
- "",
- " <file><rank>-<file><rank>"
- }, [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()
Add Comment
Please, Sign In to add comment