Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework of the solver #6

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
# Test dataset from Pascal Pons' tutorial
test

# Test files
SolverTest.java
PlayTest.java
112 changes: 32 additions & 80 deletions Main.java
Original file line number Diff line number Diff line change
@@ -1,94 +1,46 @@
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Scanner;
import java.io.*;

// custom import
import util.*;

public class Main {
public static void main(String[] args) throws Exception {
// create objects
Position pos = new Position();
public static void main(String[] args) {
Solver solver = new Solver();

Scanner in = new Scanner(System.in);

// clear console
ClearConsole.clear();

// read in the text from instructions.txt and print the instructions
File file = new File("instructions.txt");
BufferedReader br = new BufferedReader(new FileReader(file));

String line;
try {
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}

br.close();
Scanner scanner = new Scanner(System.in);
System.out.print("Enter test batch to run: ");
String testBatch = scanner.nextLine();
scanner.close();

// newline
System.out.println();

// generate a random move string of length between 10 and 20
// keep generating a random move string until the string doesn't have any winning moves
int length = (int) (Math.random() * 10 + 10);
String moveString = "";
try {
BufferedReader br = new BufferedReader(new FileReader("test/" + testBatch));
String line;

while (true) {
Position sudoPosition = new Position();
System.out.println("Running test batch " + testBatch + "...");

for (int i = 0; i < length; i++) {
moveString += (int) (Math.random() * 7 + 1);
}

int moveCount = sudoPosition.play(moveString);
if (moveCount == moveString.length()) {
break;
} else {
moveString = "";
while ((line = br.readLine()) != null) {
String[] split = line.split(" ");
String seq = split[0];
int expectedScore = Integer.parseInt(split[1]);

Position P = new Position();
P.play(seq);

int score = solver.solve(P);

if (score != expectedScore) {
System.out.println("Error: " + seq + " " + score + " " + expectedScore);
br.close();
return;
}

System.out.println(seq + " " + score);
}
}

// get user input for the move string and the heuristic setting
System.out.print("Do you want to use a random move string? (y/n): ");
String input = in.nextLine();
br.close();

if (input.equals("y")) {
System.out.println("Using random move string: " + moveString);
} else {
System.out.print("Enter the move string: ");
moveString = in.nextLine();
System.out.println("Test batch " + testBatch + " passed!");
} catch (Exception e) {
System.out.println(e);
}

System.out.print("Enter a heuristic setting (0 for weak, 1 for strong): ");
boolean weak = (in.nextInt() == 0);

// newline
System.out.println();

System.out.println("Solving...");

// newline
System.out.println();

// play the move string
int moveCount = pos.play(moveString);
if (moveCount != moveString.length()) {
System.out.println("Invalid move: shutting down");
} else {
// solve the position
int startTime = (int) System.currentTimeMillis();
int score = solver.solve(pos, weak);
int endTime = (int) System.currentTimeMillis();

System.out.println("Position solved in " + pos.getMoves() + " moves in " + (endTime - startTime) + "ms: Score = " + score);
System.out.println(ConsoleColors.GREEN + "Optimal next move: column " + solver.chooseMove(pos, weak) + ConsoleColors.RESET);
}

in.close();
}
}
30 changes: 0 additions & 30 deletions MoveSorter.java

This file was deleted.

169 changes: 48 additions & 121 deletions Position.java
Original file line number Diff line number Diff line change
@@ -1,166 +1,93 @@
public class Position {
public static final int WIDTH = 7; // width of the board
public static final int HEIGHT = 6; // height of the board
public static final int WIDTH = 7;
public static final int HEIGHT = 6;
public static final int MIN_SCORE = -(WIDTH * HEIGHT) / 2 + 3;
public static final int MAX_SCORE = (WIDTH * HEIGHT + 1) / 2 - 3;

private long current_position;
private long mask;
private int moves; // number of moves played since the beinning of the game

public static final long bottom(int width, int height) {
return width == 0 ? 0 : bottom(width - 1, height) | 1L << (width - 1) * (height + 1);
}
private int moves;

/** Masks */
private static final long bottom_mask = bottom(WIDTH, HEIGHT);
private static final long board_mask = bottom_mask * ((1L << HEIGHT) - 1);

public static final long column_mask(int col) {
return ((1L << HEIGHT) - 1) << col * (HEIGHT + 1);
}

private static final long top_mask_col(int col) {
return 1L << ((HEIGHT - 1) + col * (HEIGHT + 1));
}

private static final long bottom_mask_col(int col) {
return 1L << (col * (HEIGHT + 1));
}

/** Constructor */
public Position() {
current_position = 0;
mask = 0;
moves = 0;
}

public Position(Position pos) {
current_position = pos.current_position;
mask = pos.mask;
moves = pos.moves;
public Position(Position P) {
current_position = P.current_position;
mask = P.mask;
moves = P.moves;
}

/** Play a move
* @param move the move to play
*/
public void play(long move) {
current_position ^= mask;
mask |= move;
moves++;
public boolean playable(int col) {
return (mask & top_mask(col)) == 0;
}

public void play(int col) {
play(column_mask(col));
current_position ^= mask;
mask |= (mask + bottom_mask(col));
moves++;
}

/** Play a sequence of moves
* @param seq the sequence of moves to play
*/
public int play(String seq) {
for (int i = 0; i < seq.length(); i++) {
int column = seq.charAt(i) - '1';
if (column < 0 || column >= Position.WIDTH || !canPlay(column) || isWinningMove(column)) return i; // invalid move
playColumn(column);
}
return seq.length();
}

/** Check if the current player can win in the next move */
public boolean canWinNext() {
return (winning_position() & possible()) != 0;
}

public long possibleNonLosingMoves() {
long possible_mask = possible();
long opponent_win = opponent_winning_position();
long forced_moves = possible_mask & opponent_win;

if (forced_moves != 0) {
if ((forced_moves & (forced_moves - 1)) != 0) {
return 0;
} else {
possible_mask = forced_moves;
int col = Character.getNumericValue(seq.charAt(i)) - 1;
if (col < 0 || col >= WIDTH || !playable(col) || winsByPlaying(col)) {
return i;
}
play(col);
}

return possible_mask & ~(opponent_win >> 1);
}

public int moveScore(long move) {
return popcount(compute_winning_position(current_position | move, mask));
return seq.length();
}

public boolean canPlay(int column) {
return (mask & top_mask_col(column)) == 0;
public boolean winsByPlaying(int col) {
long pos = current_position;
pos |= (mask + bottom_mask(col)) & column_mask(col);
return alignment(pos);
}

/** Getters */
public int getMoves() {
return moves;
}

public long getKey() {
public long key() {
return current_position + mask;
}

private void playColumn(int col) {
play((mask + bottom_mask_col(col)) & column_mask(col));
}
private boolean alignment(long pos) {
long m = pos & (pos >> (HEIGHT + 1));
if ((m & (m >> (2 * (HEIGHT + 1)))) != 0) {
return true;
}

private boolean isWinningMove(int column) {
return (winning_position() & possible() & column_mask(column)) != 0;
}
m = pos & (pos >> HEIGHT);
if ((m & (m >> (2 * HEIGHT))) != 0) {
return true;
}

private long winning_position() {
return compute_winning_position(current_position, mask);
}
m = pos & (pos >> (HEIGHT + 2));
if ((m & (m >> (2 * (HEIGHT + 2)))) != 0) {
return true;
}

private long opponent_winning_position() {
return compute_winning_position(current_position ^ mask, mask);
}
m = pos & (pos >> 1);
if ((m & (m >> 2)) != 0) {
return true;
}

private long possible() {
return (mask + bottom_mask) & board_mask;
return false;
}

private static int popcount(long m) {
int c = 0;

for (c = 0; m != 0; c++) {
m &= m - 1;
}
private long top_mask(int col) {
return (1L << (HEIGHT - 1)) << col * (HEIGHT + 1);
}

return c;
private long bottom_mask(int col) {
return 1L << col * (HEIGHT + 1);
}

private long compute_winning_position(long position, long mask) {
// vertical
long r = (position << 1) & (position << 2) & (position << 3);

//horizontal
long p = (position << (HEIGHT + 1)) & (position << 2 * (HEIGHT + 1));
r |= p & (position << 3 * (HEIGHT + 1));
r |= p & (position >> (HEIGHT + 1));
p = (position >> (HEIGHT + 1)) & (position >> 2 * (HEIGHT + 1));
r |= p & (position << (HEIGHT + 1));
r |= p & (position >> 3 * (HEIGHT + 1));

//diagonals
p = (position << HEIGHT) & (position << 2 * HEIGHT);
r |= p & (position << 3 * HEIGHT);
r |= p & (position >> HEIGHT);
p = (position >> HEIGHT) & (position >> 2 * HEIGHT);
r |= p & (position << HEIGHT);
r |= p & (position >> 3 * HEIGHT);

p = (position << (HEIGHT + 2)) & (position << 2 * (HEIGHT + 2));
r |= p & (position << 3 * (HEIGHT + 2));
r |= p & (position >> (HEIGHT + 2));
p = (position >> (HEIGHT + 2)) & (position >> 2 * (HEIGHT + 2));
r |= p & (position << (HEIGHT + 2));
r |= p & (position >> 3 * (HEIGHT + 2));

return r & (board_mask ^ mask);
private long column_mask(int col) {
return ((1L << HEIGHT) - 1) << col * (HEIGHT + 1);
}
}
}
Loading