diff --git a/qchess/qchess.py b/qchess/qchess.py old mode 100644 new mode 100755 index 092a121dedd871abede9903298d324cdf4f247fd..da4cab7d8b2e2bb37cb10010c5682af3a21f7bab --- a/qchess/qchess.py +++ b/qchess/qchess.py @@ -890,9 +890,11 @@ class HumanPlayer(Player): sys.stdout.write("SELECTION?\n") try: p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + return p except: sys.stderr.write("ILLEGAL GIBBERISH\n") continue + # It's your move captain def get_move(self): if isinstance(graphics, GraphicsThread): @@ -908,6 +910,7 @@ class HumanPlayer(Player): sys.stdout.write("MOVE?\n") try: p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + return p except: sys.stderr.write("ILLEGAL GIBBERISH\n") continue @@ -1673,11 +1676,10 @@ class GameThread(StoppableThread): self.cond = threading.Condition() # conditional for some reason, I forgot self.final_result = "" self.server = server + self.retry_illegal = False - - - + # Run the game (run in new thread with start(), run in current thread with run()) def run(self): @@ -1804,14 +1806,17 @@ class GameThread(StoppableThread): break except Exception,e: #if False: - result = e.message - sys.stderr.write("qchess.py exception: "+result + "\n") - - self.stop() + - with self.lock: - self.final_result = self.state["turn"].colour + " " + e.message - break + result = e.message + if self.retry_illegal: + self.state["turn"].update(result); + else: + sys.stderr.write("qchess.py exception: "+result + "\n") + self.stop() + with self.lock: + self.final_result = self.state["turn"].colour + " " + e.message + break @@ -2701,6 +2706,7 @@ def main(argv): global sleep_timeout + retry_illegal = False server_addr = None max_moves = None @@ -2798,7 +2804,8 @@ def main(argv): sleep_timeout = -1 else: sleep_timeout = float(arg[2:].split("=")[1]) - + elif (arg[1] == '-' and arg[2:] == "retry-illegal"): + retry_illegal = not retry_illegal elif (arg[1] == '-' and arg[2:] == "help"): # Help os.system("less data/help.txt") # The best help function @@ -2845,6 +2852,7 @@ def main(argv): board = Board(style) board.max_moves = max_moves game = GameThread(board, players) + game.retry_illegal = retry_illegal @@ -2987,4 +2995,4 @@ if __name__ == "__main__": # --- main.py --- # -# EOF - created from make on Sun May 19 12:36:10 WST 2013 +# EOF - created from make on Thursday 20 June 18:09:07 WST 2013 diff --git a/qchess/src/game.py b/qchess/src/game.py index 73adfe75cb078c1c87bc08b231ce2b2cacc2db6f..15c53b1315086129fcc8dd21497b1ad7b3393bd9 100644 --- a/qchess/src/game.py +++ b/qchess/src/game.py @@ -15,11 +15,10 @@ class GameThread(StoppableThread): self.cond = threading.Condition() # conditional for some reason, I forgot self.final_result = "" self.server = server + self.retry_illegal = False - - - + # Run the game (run in new thread with start(), run in current thread with run()) def run(self): @@ -146,14 +145,17 @@ class GameThread(StoppableThread): break except Exception,e: #if False: - result = e.message - sys.stderr.write("qchess.py exception: "+result + "\n") - - self.stop() + - with self.lock: - self.final_result = self.state["turn"].colour + " " + e.message - break + result = e.message + if self.retry_illegal: + self.state["turn"].update(result); + else: + sys.stderr.write("qchess.py exception: "+result + "\n") + self.stop() + with self.lock: + self.final_result = self.state["turn"].colour + " " + e.message + break diff --git a/qchess/src/main.py b/qchess/src/main.py index 6ebf70c0972984a4a7db73cb3196a237084b940a..44aa36075a8444ace5777991b5d1141923d020ac 100644 --- a/qchess/src/main.py +++ b/qchess/src/main.py @@ -88,6 +88,7 @@ def main(argv): global sleep_timeout + retry_illegal = False server_addr = None max_moves = None @@ -185,7 +186,8 @@ def main(argv): sleep_timeout = -1 else: sleep_timeout = float(arg[2:].split("=")[1]) - + elif (arg[1] == '-' and arg[2:] == "retry-illegal"): + retry_illegal = not retry_illegal elif (arg[1] == '-' and arg[2:] == "help"): # Help os.system("less data/help.txt") # The best help function @@ -232,6 +234,7 @@ def main(argv): board = Board(style) board.max_moves = max_moves game = GameThread(board, players) + game.retry_illegal = retry_illegal diff --git a/qchess/src/player.py b/qchess/src/player.py index 346674b227bc9296ade7bba59a19945733ceb555..fffa73855df09f31f61bfd967764214e47541c78 100644 --- a/qchess/src/player.py +++ b/qchess/src/player.py @@ -265,9 +265,11 @@ class HumanPlayer(Player): sys.stdout.write("SELECTION?\n") try: p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + return p except: sys.stderr.write("ILLEGAL GIBBERISH\n") continue + # It's your move captain def get_move(self): if isinstance(graphics, GraphicsThread): @@ -283,6 +285,7 @@ class HumanPlayer(Player): sys.stdout.write("MOVE?\n") try: p = map(int, sys.stdin.readline().strip("\r\n ").split(" ")) + return p except: sys.stderr.write("ILLEGAL GIBBERISH\n") continue diff --git a/web/qwebchess/index.html b/web/qwebchess/index.html index 73b77c01865bc3bd3aa274321ab2a1d98973b1d9..aaf170fb5c6943d430a429c619b6f4db02fe3b59 100644 --- a/web/qwebchess/index.html +++ b/web/qwebchess/index.html @@ -1,26 +1,52 @@ - QChess Web UI + QChess Web UI + + + + - - -
- - - - - - - - - -
-
-
-Made By Sam Moore [SZM] and Mitchell Pomery [BG3] + + + +

Press the button to start

+ +
+ +
+
+ + +
+ +
+ + diff --git a/web/qwebchess/js.js b/web/qwebchess/js.js index 7baea20bd8f86ce938ed984ba18ea58083cc2399..db91e14d7dd7c01f6d59f0fd709e09d533653b90 100644 --- a/web/qwebchess/js.js +++ b/web/qwebchess/js.js @@ -1,11 +1,12 @@ -//progcomp.ucc.asn.au/cgi-bin/qchess.cgi?r=start -//progcomp.ucc.asn.au/cgi-bin/qchess.cgi?r=quit -//progcomp.ucc.asn.au/cgi-bin/qchess.cgi?x=X&y=Y (0 indexed) +/** + * qwebchess.js + * jQuery interface for Quantum Chess + * + * @authors Sam Moore and Mitch Pomery + */ pieceSelected = ""; // currently selected piece -piece = ""; -colour = "W"; // colour of this player -canClick = true; +playerColour = "W"; // colour of this player // Unicode representations of chess pieces pieceChar = {"W" : { "p" : "\u2659", "h" : "\u2658", "b" : "\u2657", "r" : "\u2656", "q" : "\u2655", "k" : "\u2654", "?" : "?"}, @@ -13,250 +14,393 @@ pieceChar = {"W" : { "p" : "\u2659", "h" : "\u2658", "b" : "\u2657", "r" : "\u26 emptyHTML = "     " -// Select (or move) a piece -function selectPiece(loc) { - if (!canClick) - return; - - x = (""+loc).charAt(1); - y = (""+loc).charAt(0); - //alert(loc); +gameStarted = false; +canClick = true; - // work out whether to select or move based on the comment tag for the clicked location - // It is either "" (white; select) or " (black) or "" (empty) - if (pieceSelected == "") +// jQuery foo goes in here +$(document).ready(function() +{ + // Click the start/quit button + $("#start").click(function() { - square = document.getElementById(loc); - if (square.innerHTML.charAt(4) == colour) + if (gameStarted === false) { - console.log("Piece Selected: " + loc); - pieceSelected = loc; - ajaxUpdate("x=" + x + "&y=" + y); - if ((+x + +y) % 2 == 0) - square.style.background = "#DFD"; - else - square.style.background = "#8F8"; - } - } - else { - //alert("pieceMoved"); - if (validMove(pieceSelected, piece, loc)) { - doMove(pieceSelected, loc); - ajaxUpdate("x=" + x + "&y=" + y); + gameStarted = true; + $("#board").boardLoad(); + $("#welcome").hide(); + $("#status").show(); + $("#status").html("white SELECT?"); + $("#start").html("Quit Game"); pieceSelected = ""; + canClick = true; + $.ajax({url : "/cgi-bin/qchess.cgi", data : {r : "force_quit"}, success : function() {}}); + $.ajax({url : "/cgi-bin/qchess.cgi", data : {r : "start"}}).done(function(data) {$(this).update(data)}); + + } - else { - console.log("Invalid Move"); + else + { + gameStarted = false; + $("#welcome").show(); + $("#status").html("Game over"); + $("#start").html("New Game"); + $.ajax({url : "/cgi-bin/qchess.cgi", data : {r : "quit"}, success : function() {console.log("Quit game");}}); } - } -} + }); -function resetColour(loc) -{ - square = document.getElementById(loc); - if ((+loc[loc.length-1] + +loc[loc.length-2]) % 2 == 0) - square.style.background = "#FFF"; - else - square.style.background = "#DDD"; + // bind click event to table cells + $("#board").on('click', 'td' , function(e) + { + if (canClick === false) + return; + + var id = $(this).attr("id"); + legal = true; + if (pieceSelected === "") + { + if ($(this).legalSelection()) + { + pieceSelected = id; + $(this).setSquareColour("blue"); + } + else + { + legal = false; + alert("Illegal selection " + id); + } + } + else + { + mover = $("#board").find("#"+pieceSelected); + if (mover.legalMove($(this))) + { + $("#status").html(colourString(otherColour(mover.getColour())) + " SELECT?"); + mover.move($(this)); + pieceSelected = ""; + $("#board td").each(function() {$(this).setSquareColour("default");}); + } + else + { + legal = false; + alert("Illegal move " + id); + } + } -} + if (legal) + $.ajax({url : "/cgi-bin/qchess.cgi", data : {x : id[0], y : id[1]}}).done(function(data) {$(this).update(data)}); + }); -function validMove(start, piece, end) { - return true; -} - -function doMove(start, end) { - alert("doMove("+start+","+end+")"); - s1 = document.getElementById(start); - s2 = document.getElementById(end); - s2.innerHTML = s1.innerHTML; - s1.innerHTML = emptyHTML; + $.fn.showMoves = function() + { + $(this).setSquareColour("green"); + var that = $(this); //Look [DJA]! I used it! + $("#board td").each(function() + { + if (that.legalMove($(this)) === true) // See? + { + //alert("Legal move from " + that.attr("id") + " -> " + $(this).attr("id")); + $(this).setSquareColour("red"); + } + }); + + } - resetColour(start); + // Get colour of occupied square + // W - white + // B - black + // 0 - unoccupied + $.fn.getColour = function() + { + return $(this).html()[4]; // yeah, I know this is horrible, so sue me + } - if ((+end[end.length-1] + +end[end.length-2]) % 2 == 1) + // Get type of piece + $.fn.getType = function() { - s2.innerHTML = s2.innerHTML.replace(/.*<\/bold>/i, "?"); + return $(this).html().match(/(.*)<\/bold>/)[1]; // again, I know it's horrible, so sue me } - //console.log("Piece Moved"); -} -function boardLoad() { - ajaxUpdate("r=force_quit"); - + // Get coords + $.fn.getX = function() {return Number($(this).attr("id")[0]);} + $.fn.getY = function() {return Number($(this).attr("id")[1]);} - - for (i = 0; i < 8; i++) { - for (j = 0; j < 8; j++) { - e = ""+i + "" + j; - resetColour(e); - } + // Check a square is a valid selection + $.fn.legalSelection = function() + { + return ($(this).getColour() == playerColour); } - - //Place pieces on the board - for (i = 0; i < 8; i++) { - black = document.getElementById("1" + i); - white = document.getElementById("6" + i); - //pawns - black.innerHTML = " " + pieceChar["B"]["p"] + " ? ?"; - white.innerHTML = " " + pieceChar["W"]["p"] + " ? ?"; - - black = document.getElementById("0" + i); - white = document.getElementById("7" + i); - piece = "p"; - if (i == 0 || i == 7) - piece = "r"; - if (i == 1 || i == 6) - piece = "h"; - if (i == 2 || i == 5) - piece = "b"; - if (i == 3) - piece = "k"; - if (i == 4) - piece = "q"; - //major pieces - black.innerHTML = " " + pieceChar["B"][piece] + " ? ?"; - white.innerHTML = " " + pieceChar["W"][piece] + " ? ?"; - // empty squares - for (j = 2; j < 6; j++) + // determine whether a piece can move into another square + $.fn.legalMove = function(target) + { + if (target.getColour() == $(this).getColour()) + return false; + if (target.getX() == $(this).getX() && target.getY() == $(this).getY()) + return false; + switch ($(this).getType()) { - square = document.getElementById(""+j + i); - square.innerHTML = emptyHTML; - } - } - - setTimeout(function(){ajaxUpdate("r=start");}, 1000); -} + case pieceChar["W"]['p']: + if ($(this).getY() == 6 && target.getY() == 4 && $(this).getX() == target.getX() && target.getColour() == '0') + return true; + if ($(this).getY() - target.getY() != 1 || Math.abs($(this).getX() - target.getX()) > 1) + return false; + return ($(this).getX() == target.getX() || target.getColour() != '0'); + + case pieceChar["B"]['p']: + if ($(this).getY() == 1 && target.getY() == 3 && $(this).getX() == target.getX()) + return true; + if ($(this).getY() - target.getY() != -1 || Math.abs($(this).getX() - target.getX()) > 1) + return false; + return ($(this).getX() == target.getX() || target.getColour() != '0'); -//AJAX Stuff -function ajaxUpdate(queryString) { - var ajaxRequest; // The variable that makes Ajax possible! + case pieceChar["W"]['h']: + case pieceChar["B"]['h']: + return ((Math.abs($(this).getY() - target.getY()) == 2 && Math.abs($(this).getX() - target.getX()) == 1) + || (Math.abs($(this).getX() - target.getX()) == 2 && Math.abs($(this).getY() - target.getY()) == 1)); - try { - // Opera 8.0+, Firefox, Safari - ajaxRequest = new XMLHttpRequest(); - } catch (e) { - // Internet Explorer Browsers - try { - ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP"); - } catch (e) { - try { - ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) { - // Something went wrong - alert("Your Browser is not Ajax Compatible, Please Upgrade to Google Chrome."); + case pieceChar["W"]['k']: + case pieceChar["B"]['k']: + return (Math.abs($(this).getX() - target.getX()) <= 1 && Math.abs($(this).getY() - target.getY()) <= 1); + case pieceChar["W"]['b']: + case pieceChar["B"]['b']: + //console.log("" + Math.abs($(this).getX() - target.getX()) + " vs " + Math.abs($(this).getY() - target.getY())); + if (Math.abs($(this).getX() - target.getX()) != Math.abs($(this).getY() - target.getY())) + return false; + break; + case pieceChar["W"]['r']: + case pieceChar["B"]['r']: + //console.log("" + Math.abs($(this).getX() - target.getX()) + " vs " + Math.abs($(this).getY() - target.getY())); + console.log("Rook"); + if (Math.abs($(this).getX() - target.getX()) != 0 && Math.abs($(this).getY() - target.getY()) != 0) + return false; + break; + case pieceChar["W"]['q']: + case pieceChar["B"]['q']: + //console.log("" + Math.abs($(this).getX() - target.getX()) + " vs " + Math.abs($(this).getY() - target.getY())); + if (Math.abs($(this).getX() - target.getX()) != Math.abs($(this).getY() - target.getY())) + { + if (Math.abs($(this).getX() - target.getX()) != 0 && Math.abs($(this).getY() - target.getY()) != 0) + return false; + } + break; + default: return false; - } } + console.log("scanning"); + var vx = ($(this).getX() == target.getX()) ? 0 : (($(this).getX() < target.getX()) ? 1 : -1); + var vy = ($(this).getY() == target.getY()) ? 0 : (($(this).getY() < target.getY()) ? 1 : -1); + var x = $(this).getX() + vx; var y = $(this).getY() + vy; + while ((x != target.getX() || y != target.getY()) && x >= 0 && y >= 0 && x < 8 && y < 8) + { + var c = $("#"+x+""+y).getColour(); + if (c === "W" || c === "B") + { + console.log("Blocked at "+x+""+y); + return false; + } + else + console.log("Scan ok at "+x+""+y); + x += vx; + y += vy; + } + return true; } - - //alert(queryString); - - // Create a function that will receive data sent from the server - ajaxRequest.onreadystatechange = function () + + // Move square to another + $.fn.move = function(dest) { - //alert("RS" + ajaxRequest.readyState); - if (ajaxRequest.readyState == 4) { - console.log("AJAX Response: " + ajaxRequest.responseText); - lines = ajaxRequest.responseText.split("\n"); + dest.html($(this).html()); + $(this).html(emptyHTML); - for (var i = 0; i < lines.length; ++i) - { - tokens = lines[i].split(" ") - x = Number(tokens[0]); + // Collapse into quantum state if on a black square + if ((dest.getX() + dest.getY()) % 2 != 0 && (dest.html()[0] == '?' || dest.html()[dest.html().length-1] == '?')) + { + oldHTML = dest.html(); + dest.html(oldHTML.replace(/.*<\/bold>/i, "?")); + } + } - if (isNaN(tokens[0]) || isNaN(tokens[1])) - continue; + // Interpret AJAX response + $.fn.update = function(data) + { + console.log("AJAX Response:\n"+data); + var lines = data.split("\n"); + for (var i = 0; i < lines.length; ++i) + { + var tokens = lines[i].split(" "); - var s1 = document.getElementById("" + tokens[1] + "" + tokens[0]); - var s2 = document.getElementById("" + tokens[4] + "" + tokens[3]); - if (tokens[2] == "->" && s1.innerHTML.charAt(4) != '0') + if (!isNaN(tokens[0]) && !isNaN(tokens[1])) + { + s1 = $("#board").find("#"+tokens[0]+tokens[1]) + if (tokens[2] === "->") { - canClick = false; - if ((+tokens[0] + +tokens[1]) % 2 == 0) - s1.style.background = "#DFD"; - else - s1.style.background = "#8F8"; - - var doThisMove = function(start, end) {doMove(start, end); canClick = true;}(""+tokens[1]+""+tokens[0], ""+tokens[4]+""+tokens[3]); - setTimeout(function() {doThisMove(); canClick = true;}, 500); + if (s1.html()[4] != '0') + { + s2 = $("#board").find("#"+tokens[3]+tokens[4]); + canClick = false; + setTimeout((function(x) + { + return function() + { + s1.move(x); + $("#board td").each(function() {$(this).setSquareColour("default");}); + x.setSquareColour("blue"); + setTimeout((function(xx) + { + return function() + { + xx.setSquareColour("default"); canClick = true; + $("#status").html(colourString(playerColour) + " SELECT?"); + }; + }(x)), 500); + }; + }(s2)), 500); + } } - else if (tokens.length == 4 && !isNaN(tokens[0]) && !isNaN(tokens[1]) && !isNaN(tokens[2]) && isNaN(tokens[3])) + else if (tokens.length === 4 && !isNaN(tokens[2]) && isNaN(tokens[3])) { - html = s1.innerHTML; - c = html.charAt(4); - piece = tokens[3]; - if (piece == "knight") //HACK - piece = "h"; - else - piece = ""+piece.charAt(0); + var t = "h"; + if (tokens[3] != "knight") + t = tokens[3][0]; + + var oldHTML = s1.html(); + var c = s1.getColour(); if (tokens[2] == "1") - html[html.length-1] = pieceChar[c][piece]; - - s1.innerHTML = html.replace(/.*<\/bold>/i, ""+pieceChar[c][piece]+""); - } - } - - /* - if (ret.charAt(4) == "-" && ret.charAt(5) == ">") { - //Piece has been moved - //console.log("Moving other piece"); - lines = ret.split("\n"); - //if (lines[3] != "SELECT?") { - if (lines[2] != "SELECT?") { - x1 = lines[2].charAt(0); - y1 = lines[2].charAt(2); - x2 = lines[2].charAt(7); - y2 = lines[2].charAt(9); - console.log("Black Move: " + x1 + "" + y1 + " -> " + x2 + "" + y2); - doMove(y1 + "" + x1, y2 + "" + x2); - } - else { - console.log("Black Unable to move"); - } - } - else { - lines = ret.split("\n"); - if (lines[1] == "MOVE?") { - //We selected a piece - //console.log("choose where to move our piece"); - piece = lines[0].charAt(6); - //console.log("Piece: " + piece); - content = document.getElementById(pieceSelected); - contentHTML = content.innerHTML; - //contentHTML = contentHTML.replace("?", piece); - //"W
p ? ?"; - if (lines[0].charAt(4) == "1") { - //console.log("changing quantum piece"); - contentHTML = replaceAt(contentHTML, 44, piece); + { + oldHTML = oldHTML.substring(0, oldHTML.length-1)+pieceChar[c][t]; } - contentHTML = replaceAt(contentHTML, 28, piece); - //console.log(contentHTML); - //contentHTML = "CHANGED" + contentHTML; - content.innerHTML = contentHTML; + s1.html(oldHTML.replace(/.*<\/bold>/i, ""+pieceChar[c][t]+"")); + //console.log(oldHTML + " ==> " + s1.html()); + s1.setSquareColour("green"); + s1.showMoves(); + $("#status").html(colourString(s1.getColour()) + " MOVE?"); + } } - */ - //alert(ret); + else switch (lines[i]) + { + case "SELECT?": + pieceSelected = ""; + case "MOVE?": + case "": + case "New game.": + break; + default: + alert("Game ends: " + lines[i]); + gameStarted = false; + $("#start").html("New Game"); + $("#status").html("Game over"); + //$("#board").html(""); + + + break; + } } } - - //ar = "http://progcomp.ucc.asn.au/cgi-bin/qchess.cgi?" + queryString; - ar = "/../../../cgi-bin/qchess.cgi?" + queryString; - - console.log("AJAX Request: " + ar); - - ajaxRequest.open("GET", ar, true); - ajaxRequest.send(); -} + //Reset the colour of a square + $.fn.setSquareColour = function(type) + { + var colour = "000000"; + switch (type) + { + case "blue": + colour = "5555aa"; + break; + case "green": + colour = "55aa55"; + break; + case "red": + colour = "aa5555"; + break; + default: + colour = "aaaaaa"; + break; + } + + id = $(this).attr("id"); + if ((Number(id[0]) + Number(id[1])) % 2 == 0) + { + colour = addHexColour(colour, "555555"); + } + $(this).css("background-color", "#"+colour); + } + + // Loads the board + $.fn.boardLoad = function() + { + boardHTML = ""; + for (var y = 0; y < 8; ++y) + { + boardHTML += ""; + for (var x = 0; x < 8; ++x) + { + boardHTML += ""+emptyHTML+""; + } + boardHTML += ""; + } + $(this).html(boardHTML); + + $(this).find("td").each(function() + { + $(this).setSquareColour("default"); + }); + // Add pieces + for (var x = 0; x < 8; ++x) + { + // pawns + $(this).find("#"+x+"1").html(" "+pieceChar["B"]["p"]+" ? ?"); + $(this).find("#"+x+"6").html(" "+pieceChar["W"]["p"]+" ? ?"); + + t = "?"; + switch (x) + { + case 0: + case 7: + t = 'r'; + break; + case 1: + case 6: + t = 'h'; + break; + case 2: + case 5: + t = 'b'; + break; + case 4: + t = 'q'; + break; + } + if (x == 3) + continue; + $(this).find("#"+x+"0").html(" "+pieceChar["B"][t]+" ? ?"); + $(this).find("#"+x+"7").html(" "+pieceChar["W"][t]+" ? ?"); + } + t = pieceChar["B"]["k"]; + $(this).find("#30").html(" "+t+" "+t+" "+t); + t = pieceChar["W"]["k"]; + $(this).find("#37").html(" "+t+" "+t+" "+t); + + } +}); +// Add two hex colours +function addHexColour(c1, c2) +{ + var hexStr = (parseInt(c1, 16) + parseInt(c2, 16)).toString(16); + while (hexStr.length < 6) { hexStr = '0' + hexStr; } // Zero pad. + return hexStr; +} + +function colourString(c) +{ + return (c == "W") ? "white" : "black"; +} -function replaceAt(s, n, t) { - //console.log(s.substring(0, n) + "\n" + t + "\n" + s.substring(n + 1) + "\n"); - return (s.substring(0, n) + t + s.substring(n + 1)); +function otherColour(c) +{ + return (c == "W") ? "B" : "W"; }