Skip to content

Instantly share code, notes, and snippets.

@hkailahi
Last active March 18, 2017 21:46
Show Gist options
  • Save hkailahi/f0ded23bd81a03eec6790aa8cdd0dca6 to your computer and use it in GitHub Desktop.
Save hkailahi/f0ded23bd81a03eec6790aa8cdd0dca6 to your computer and use it in GitHub Desktop.
TicTacToe implementation: TicTacToe.java holds the main(). GameBoard composed of 9 GameSpaces. Player 1 and Player 2 take turns calling a capture method on available GameSpaces.
public class GameBoard {
private GameSpace[] spaces = {new GameSpace(1), new GameSpace(2), new GameSpace(3),
new GameSpace(4), new GameSpace(5), new GameSpace(6),
new GameSpace(7), new GameSpace(8), new GameSpace(9)};
public GameBoard() {}
public GameSpace getGameSpace(int index) {
return spaces[index];
}
public GameSpace[] getGSList() {
return spaces;
}
public void printGameBoard() {
int posFmt; // Used for formatting logic based on element's position on board
for(int i=1; i<=9; i++) {
posFmt = i%3;
if (posFmt==2 || posFmt==0)
System.out.print(" |");
System.out.print(" " + spaces[i-1].getMark());
if(posFmt==0 && i < 7)
System.out.println("\n-----------------------------------------------------");
}
System.out.println("\n");
}
public boolean isItOver(String currPlayerMarker, int turnCounter) {
if (turnCounter < 5) return false;
if (isWon(currPlayerMarker)) {
System.out.print("\nCongrats! Player ");
if(currPlayerMarker.equals("X"))
System.out.println("1 has won the game!");
else
System.out.println("2 has won the game!");
return true;
}
if (turnCounter==9) { // Declares tie if final move wasn't a winning move
System.out.println("\nLooks like it ended in a tie.");
return true;
}
return false;
}
public boolean isWon(String currPlayerMarker) {
for (int i=0; i<=2; i++) { // Check vertical
if (checkMatch(i, i+3, i+6, currPlayerMarker)) {
return true;
}
}
for (int i=0; i<=6; i+=3) { // Check horizontal
if (checkMatch(i, i+1, i+2, currPlayerMarker)) {
return true;
}
}
// Check first of two diagonals
if (checkMatch(0, 4, 8, currPlayerMarker)) {
return true;
}
// Check second of two diagonals
if (checkMatch(2, 4, 6, currPlayerMarker)) {
return true;
}
return false;
}
public boolean checkMatch(int p1, int p2, int p3, String currPlayerMarker ) {
return spaces[p1].getMark().equals(currPlayerMarker)
&& spaces[p2].getMark().equals(currPlayerMarker)
&& spaces[p3].getMark().equals(currPlayerMarker);
}
}
public class GameSpace {
private int position;
private String marking;
private boolean isCaptured;
public GameSpace(int position) {
this.position = position;
this.marking = Integer.toString(position);
this.isCaptured = false;
}
public int getPosition() {
return this.position;
}
public void setCaptured(String playerMarker) {
this.marking = playerMarker;
this.isCaptured = true;
}
public String getMark() {
return this.marking;
}
public boolean isCaptured() {
return isCaptured;
}
}
import java.util.ArrayList;
import java.util.List;
public class Player {
private static List<GameSpace> available = new ArrayList<>();
private List<GameSpace> captured = new ArrayList<>();
private String marker;
private boolean isTurn;
public Player(String marker) {
this.marker = marker;
this.isTurn = false;
}
public static void initAvailable(GameBoard gb) {
for (GameSpace gs : gb.getGSList()) {
available.add(gs);
}
}
public void printAvailable() {
StringBuilder sb = new StringBuilder("The available choices are ");
String comma = "";
for(GameSpace gs : available) {
sb.append(comma);
sb.append(gs.getMark());
comma = ", ";
}
String old = ", ";
String replace;
if ( available.size() == 2 ) {
replace = " and ";
sb.replace(sb.lastIndexOf(old), sb.lastIndexOf(old)+old.length(), replace);
}
else if ( available.size() == 1 ) {
old = "choices are";
replace = "choice is";
sb.replace(sb.lastIndexOf(old), sb.lastIndexOf(old)+old.length(), replace);
}
else if ( !available.isEmpty() ) {
replace = ", and ";
sb.replace(sb.lastIndexOf(old), sb.lastIndexOf(old)+old.length(), replace);
}
System.out.println(sb.toString());
}
public void printCaptured() {
StringBuilder sb = new StringBuilder("Your captured spaces are ");
String comma = "";
for(GameSpace gs : captured) {
sb.append(comma);
sb.append(gs.getPosition());
comma = ", ";
}
String old = ", ";
String replace;
if ( captured.size() == 2 ) {
replace = " and ";
sb.replace(sb.lastIndexOf(old), sb.lastIndexOf(old)+old.length(), replace);
}
else if ( captured.size() == 1 ) {
old = "spaces are";
replace = "space is";
sb.replace(sb.lastIndexOf(old), sb.lastIndexOf(old)+old.length(), replace);
}
else if ( !captured.isEmpty() ) {
replace = ", and ";
sb.replace(sb.lastIndexOf(old), sb.lastIndexOf(old)+old.length(), replace);
}
System.out.println(sb.toString());
}
public void capture(GameSpace gs) {
available.remove(gs);
captured.add(gs);
gs.setCaptured(getMarker());
endTurn();
}
public String getMarker() {
return this.marker;
}
public boolean isTurn() {
return this.isTurn;
}
public void setTurn(boolean isTurn) {
this.isTurn = isTurn;
}
public void endTurn() {
this.isTurn = false;
}
}

Post Interview Refactoring


Quick Braindump 3.18:+1hr

  • playTurn() has to work in Player now that it has been moved from TTT
  • Pair review brought up verbosity, I can do better here
  • Override playTurn() in CPUPlayer
    • It should have logic for picking (random) available at first impl and then blocking opponent
      • It doesn’t need to do all the input checks because it should never pick some unavailable option anyways
      • Impl details straightforward, if difficult then Player is probably too complicated
  • In GameBoard
    • Refactor isWon so that only checks the last played position, rather than entire board for the last played position’s marker
    • Line 3, don’t have such a verbose way of making spaces array
      • Pairer compared to python way, look into
        • Use a for loop in constructer to populate array?
        • We know array size is 9, so if we need to we can declare it with that and fill with zeroes
        • Try not create unnecessary objects with “new” if possible
  • Refactor all things so it looks more intuitive
    • Should be able to read without crazy nested boolean logic
    • Rename variables and logic so that everything behaves exactly how you would expect it to
  • CPUPlayer
    • Should make own decisions in overridden playTurn()
    • Should end own turn
  • TicTacToe
    • Should have option for two player or play vs CPU
  • See if all private members are necessary
  • Put method clutter in better place, like getters/setters/printing at bottom
  • If possible, have print methods close to each other
    • If not, at least distribute them in relatively the same place inside each class
  • Do we need isBoolean-type members if we have isBoolean-type methods?
  • Make sure the idea of Gamespaces held in Gameboard is easy to quickly grasp for first-time readers of code, basically int[]
    • Same with idea of Player’s “capture()”-ing gamespaces
    • Clarify or make easy the distinction between Gamespace.position and Gamespace.marking
      • I have to switch between 0+ index and 1+ index. This needs to be blatantly obvious or per case I need to decide whether or not I really need to change between them
  • There are a lot of spaces where ...(forgot what this thought was)
  • Try to cut down on verbosity
    • But don’t sacrifice and fight the language
      • Thoughts particularly with array vs ArrayList
        • I think array is easier to reason about with 0 index in the case of this game, and ArrayList is slower and adds verbosity
        • ArrayList has add() which may be easier to reason about initially, and also contains()
          • However, ArrayList.get(index).getPosition().equals(“foo”) with other calls inside of some already large comparison matrix will be difficult for reader to follow rather than a[index].equals(“foo”) or a[i]==“foo”
  • Are static methods being used correctly
    • protected static … available[]… seems wildly sketchy, but it seems to make sense here
      • Rethink needed here
  • Don’t overthink refactor though, turn it in ASAP
    • Decision has to be made in few days or I miss batch
    • set deadline when I get back home
  • Lots of using modulus may be getting too clever, probably really un-clever in reality
    • Rethink needed here
  • Lots of parameters getting passed around
    • Do I really need to pass all the things I am?
    • Player class may have sufficient information to operate without most of passing
      • But.. is it smart to do more cycles on that info rather than simply doing calcs on passed values that may have been processed? Consider this.
  • TicTacToe should have been a really simple game
    • Why did it take me so much code? Cut out unnecessary code, walk through steps of game
    • Consider/Approach: How much do we know at step "n"? What can we do with this information? Are we best utilizing available knowledge at step "n"? Can step "n" and n-1 or n+1 be combined? Minimize cognitive load, aim for simple+intuitive. * First impl code size was much smaller, but ballooned when I was thinking of how I would want to extend Player for AiPlayer in interview. Was first approach better?
Before your interview, write a program that lets two humans play a game of Tic Tac Toe in a terminal. The program should let the players take turns to input their moves. The program should report the outcome of the game.
During your interview, you will pair on adding support for a computer player to your game. You can start with random moves and make the AI smarter if you have time.
import java.util.InputMismatchException;
import java.util.Scanner;
public class TicTacToe {
private static int turnCounter = 0;
private static boolean cont = true;
public static void main(String[] args) {
GameBoard gb = new GameBoard();
Player.initAvailable(gb); // Static list of all available options on the GameBoard
System.out.println("\nLet's play Tic-Tac-Toe!\n" +
"\nThis is a two player game. Player 1 will use X's and Player 2 will use O's.\n" +
"\nWhen it is your turn, please pick an available spot by entering a single number from 1 to 9.\n" +
"\nHere's your gameboard.\n");
gb.printGameBoard();
System.out.println("\nOk let's play!\n");
Scanner sc = new Scanner(System.in);
Player p1 = new Player("X");
Player p2 = new Player("O");
p1.setTurn(true);
boolean nextTurn;
while ( cont && !gb.isItOver( (p1.isTurn() ? p1.getMarker() : p2.getMarker()), turnCounter) && turnCounter < 9 ) {
if (turnCounter%2==0) {
nextTurn = playTurn(gb, p1, sc);
if (nextTurn) p1.endTurn();
}
else {
nextTurn = playTurn(gb, p2, sc);
if (nextTurn) p2.endTurn();
}
System.out.println();
}
sc.close();
}
public static boolean playTurn(GameBoard gb, Player player, Scanner sc) {
String currPlayer = (player.getMarker().equals("X") ? "1" : "2");
int opt;
System.out.println("Player " + currPlayer + ", please select an available number: ");
try {
opt = sc.nextInt();
if (opt < 1 || opt > 9) {
System.out.print("Whoops. This number is outside of 1 through 9. ");
player.printAvailable();
return false;
}
if (gb.getGameSpace(opt-1).isCaptured()) {
System.out.print("Sorry. This number has already been selected. ");
player.printAvailable();
return false;
}
else {
player.capture(gb.getGameSpace(opt-1));
// player.printCaptured();
gb.printGameBoard();
turnCounter++;
if ( gb.isItOver(player.getMarker(), turnCounter) ) {
cont = false;
}
return true;
}
} catch (InputMismatchException e) {
System.out.print("Uh oh! Invalid input! ");
player.printAvailable();
sc.next();
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment