I made a Connect4 game recently and my Connect4 doesn't win the game when it's connected diagonally towards the right. And it only works for some combinations when it's connected diagonally to the left. Coordinates:- Top left: (0,0), Bottom left: (5,0), Top right: (0,6), Bottom right: (5,6). The Connect4 board is 6 by 7.
Problem: Connecting diagonally towards left works fine for only some combinations. But, none of the connections connected diagonally towards right work.
/** A method of winning diagonally towards the left side when playing connect 4 two player*/
/** Giving the new method with all the possible possibilities for a user to win diagonally-left */
public static void diagWinningLeft() {
for (int x = 5; x > 2; x--) { // Checks to see if same colored pegs are lining diagonally to the left
for (int y = 6; y > 3; y--) {
if (board[x][y] == 1 && board[x - 1][y - 1] == 1 && board[x - 2][y - 2] == 1 && board[x - 3][y - 3] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-left in a row in " +(countForRed)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
if (board[x - 1][y - 1] == 1 && board[x - 2][y - 2] == 1 && board[x - 3][y - 3] == 1 && board[x - 4][y - 4] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-left in a row in " +(countForRed)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
if (board[x][y] == 2 && board[x - 1][y - 1] == 2 && board[x - 2][y - 2] == 2 && board[x - 3][y - 3] == 2) {
JOptionPane.showMessageDialog(null, playerNames[1]+ " has connected four diagonally-left in a row in " +(countForYellow)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
}
}
}
/** Another method of winning diagonally towards the right side when playing connect 4 two player*/
/** Giving the new method with all the possible possibilities for a user to win diagonally-right*/
public static void diagWinningRight() {
for (int x = 0; x < 2; x++) { // Check to see if same colored pegs are lining diagonally to the right
for (int y = 0; y < 3; y++) {
if (board[x][y] == 1 && board[x + 1][y + 1] == 1 && board[x + 2][y + 2] == 1 && board[x + 3][y + 3] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-right in a row in " +(countForRed)+ " turns!");
}
if (board[x][y] == 2 && board[x + 1][y + 1] == 2 && board[x + 2][y + 2] == 2 && board[x + 3][y + 3] == 2) {
JOptionPane.showMessageDialog(null, playerNames[1]+" has connected four diagonally-right in a row in " +(countForYellow)+ " turns!");
}
}
}
}
Forgive me for not directly answering the question, but this is stuff that will help you fix it and end up with better code and a better ability to write code in future.
Extracting the logic of your "if" condition into a separate method makes it easier to think about that logic on its own, and lets you test it independently of the rest of the program.
So instead of:
if (board[x][y] == 1 && board[x - 1][y - 1] == 1 && board[x - 2][y - 2] == 1 && board[x - 3][y - 3] == 1) {
JOptionPane.showMessageDialog(...)
}
... use:
if(isDiagonalLeft(x,y,1) { ... }
... and ...
boolean isDiagonalLeft(int x, int y, int player) {
return board[x][y] == player &&
board[x - 1][y - 1] == player &&
board[x - 2][y - 2] == player &&
board[x - 3][y - 3] == player
}
Now you can run unit tests on isDiagonalLeft() to make sure it works. That is, a small program that sets up a board and just runs isDiagonalLeft() to make sure it gives the right answer in various circumstances. This feels like extra work, but most people who try it learn that it saves effort by catching bugs early.
What you've done is to somewhat separate the game logic from the presentation code (JOptionPane), so that the presentation code is not in the way when you just want to exercise the game logic. Later on in your programming studies you'll encounter ways to separate these even more, like the Model-View-Controller model.
Pulling the logic out like this also helps if you need to ask questions on Stack Overflow -- by separating the game logic from Swing, you open up the question to potential answerers who don't know anything about Swing.
And, you can re-use this method, once for each player, rather than copying the logic into two places as you have.
If it doesn't work, use the debugger in your IDE to step through it.
Now that you've done this, you can smarten up the method so that the computer does the decrementing instead of the programmer...
boolean isDiagonalLeft(int x, int y, int player) {
for(int i = 0; i<4; i++) {
if(board[x-i][y-i] != player) {
return false;
}
}
return true;
}
...and you can generalise it so that it covers both directions of diagonal:
boolean isDiagonal(int x, int y, int player, boolean direction) {
int dirUnit = direction ? -1 : 1;
for(int i = 0; i<4; i++) {
if(board[x-i][y + dirUnit] != player) {
return false;
}
}
return true;
}
... so now you can re-use the method in 4 places; for each player and for each direction.
When you come across a situation where it doesn't work in your GUI, make a unit tests that sets up the board the way it is in the GUI, and runs isDiagonal() on it. If the test passes, you know the problem is somewhere else. If the test fails, you can work with a debugger and the method's code, to make it pass.
Following code should work:
//Winning diagonally towards left
public static void diagWinningLeft() {
for (int x = 5; x > 2; x--) { // Checks to see if same colored pegs are lining diagonally to the left
for (int y = 6; y > 2; y--) {
if (board[x][y] == 1 && board[x - 1][y - 1] == 1 && board[x - 2][y - 2] == 1 && board[x - 3][y - 3] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-left in a row in " +(countForRed)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
if (board[x][y] == 2 && board[x - 1][y - 1] == 2 && board[x - 2][y - 2] == 2 && board[x - 3][y - 3] == 2) {
JOptionPane.showMessageDialog(null, playerNames[1]+ " has connected four diagonally-left in a row in " +(countForYellow)+ " turns!");
b.drawLine(x,y,x-3,y-3);
}
}
}
}
//Winning diagonally towards right
public static void diagWinningRight() {
for (int x = 5; x > 2; x--) { // Check to see if same colored pegs are lining diagonally to the right
for (int y = 0; y < 4; y++) {
if (board[x][y] == 1 && board[x - 1][y + 1] == 1 && board[x - 2][y + 2] == 1 && board[x - 3][y + 3] == 1) {
JOptionPane.showMessageDialog(null, playerNames[0]+" has connected four diagonally-right in a row in " +(countForRed)+ " turns!");
}
if (board[x][y] == 2 && board[x - 1][y + 1] == 2 && board[x - 2][y + 2] == 2 && board[x - 3][y + 3] == 2) {
JOptionPane.showMessageDialog(null, playerNames[1]+" has connected four diagonally-right in a row in " +(countForYellow)+ " turns!");
}
}
}
}
The problem with your code was that while checking towards right your coordinates(indices) continued to check for left direction(as there are 4 diagonals from a[x][y] viz. a[x-1][y-1], a[x+1][y+1] constituting diagonal in NW-SE direction(or left diagonal) and a[x-1][y+1], a[x+1][y-1] constituting diagonal in NE-SW direction(or right diagonal)).
Moreover your inner for loop didn't run to the limits.Hence, diagWinningLeft() didn't work at times..
Related
I have to implement Conway's Game of Life. Everything works as it should and given tests are passing. My only problem is that this method gives complexity error while running PMD rules on my file. I understand that so many if sentences are the cause of that, but while trying to compact them into smaller groups I accidentally broke my code.
Here's what it says:
The method 'getNeighbourCount(int, int)' has a cyclomatic complexity of 21.
The method 'getNeighbourCount(int, int)' has an NPath complexity of 20736, current threshold is 200
What would best the best options for optimizing this method?
public Integer getNeighbourCount(int x, int y) {
// x = column (20), y = row (15)
int countNeigbours = 0;
if (x != 0 && y != 0 && isAlive(x - 1,y - 1)) {
countNeigbours++;
}
if (x != 0 && isAlive(x - 1, y)) {
countNeigbours++;
}
if (x != 0 && y != rows - 1 && isAlive(x - 1,y + 1)) {
countNeigbours++;
}
if (y != 0 && isAlive(x,y - 1)) {
countNeigbours++;
}
// check self
if (y != rows - 1 && isAlive(x,y + 1)) {
countNeigbours++;
}
if (x != columns - 1 && y != 0 && isAlive(x + 1,y - 1)) {
countNeigbours++;
}
if (x != columns - 1 && isAlive(x + 1, y)) {
countNeigbours++;
}
if (x != columns - 1 && y != rows - 1 && isAlive(x + 1,y + 1)) {
countNeigbours++;
}
return countNeigbours;
}
isAlive returns the boolean if the cell is taken (true) or not (false).
Loop over the "delta x" and "delta y" from your current position:
for (int dx : new int[]{-1, 0, 1}) {
if (x + dx < 0 || x + dx >= columns) continue;
for (int dy : new int[]{-1, 0, 1}) {
if (y + dy < 0 || y + dy >= rows) continue;
if (dx == 0 && dy == 0) continue;
if (isAlive(x + dx, y + dy)) countNeighbours++;
}
}
(Of course, you don't have to use arrays and enhanced for loops: you can just do for (int dx = -1; dx <= 1; ++dx), or however else you like)
I don't know if this would provide a speed-up, but you could try having a second array which held the sums, and increased or decreased these values when setting or clearing individual cells. That replaces the many 'isAlive' checks with a check to tell if a cell should be toggled on or off, and reduces the cell adjacency computations to just those cells which were toggled, which should be many fewer than repeating the computation for the entire array. That is, for a mostly empty grid, only a small subset of cells should require recomputation, many fewer than the entire grid.
Also, you could try having whole row isActive, minActive, and maxActive values. That would further reduce the amount of looping, but would increase the complexity and cost of each iteration. The sparseness of active cells would determine whether the extra cost was balanced by the reduction in the number of iterations.
Hey guys I started programming a year ago and recently discovered streams. So I decided to complete my old tasks using streams whenever I could just to get used to them. I know it might not be smart to force using them but it's just practice.
One of my old tasks was to program Minesweeper and right now I try to find a better solution for counting adjacent mines whenever I click a field.
Some details:
I saved a bunch of mines in a Mine[] (arsenal.getArsenal()) and each of the mines has an x and y value. Whenever I click on a field I need to count all mines around the clicked field (from x-1,y-1 till x+1,y+1).
My current solutions are:
private int calculateNearby(int x, int y) {
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine -> mine.getX() == x + 1 && mine.getY() == y
|| mine.getX() == x && mine.getY() == y + 1
|| mine.getX() == x - 1 && mine.getY() == y
|| mine.getX() == x && mine.getY() == y - 1
|| mine.getX() == x - 1 && mine.getY() == y - 1
|| mine.getX() == x - 1 && mine.getY() == y + 1
|| mine.getX() == x + 1 && mine.getY() == y - 1
|| mine.getX() == x + 1 && mine.getY() == y + 1)
.count();
}
private int calculateNearby(int x, int y) {
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine ->
{
boolean b = false;
for (int i = -1; i < 2; ++i) {
for (int j = -1; j < 2; ++j) {
if ((x != 0 || y != 0) && mine.getX() == x + i && mine.getY() == y + j) {
b = true;
}
}
}
return b;
})
.count();
}
Both solutions work fine but the first looks "wrong" because of all the cases and the seconds uses for-loops which I basically tried to avoid using.
It would be nice if you could show me a better (ideally shorter) solution using streams. :D
I'm sorry if there's already a thread about this. I really tried to find anything related but there either isn't anything or I searched for the wrong keywords.
You can simplify your stream condition. Instead of checking each case if getX() equals x, x-1 or x+1 you can just check if getX() is greater or equals than x-1 and smaller or equals x+1. The same for getY().
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine -> mine.getX() >= x - 1 && mine.getX() <= x + 1
&& mine.getY() >= y - 1 && mine.getY() <= y + 1)
.count();
You could also create a method for the check to make the code more readable.
private int calculateNearby(int x, int y) {
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine -> inRange(mine.getX(), x)
&& inRange(mine.getY(), y))
.count();
}
private boolean inRange(int actual, int target) {
return actual >= target - 1 && actual <= target + 1;
}
Another way is to check if the absolute distance in each direction is less than or equal to 1:
private int calculateNearby(int x, int y) {
return (int) Arrays.stream(arsenal.getArsenal())
.filter(mine -> Math.abs(mine.getX() - x) <= 1 && Math.abs(mine.getY() - y) <= 1)
.count();
}
Note that this also counts a mine which is at the point (x, y) which is not the case with the code in the question.
When is a mine adjacent? When its only one field horizontally or vertically away.
Horizontal distance is Math.abs(mine.getX() - x), vertical distance is Math.abs(mine.getY() - y). It doesn't matter if its -1 or 1, just that it is one field away.
But it shouldn't be more than one field, either vertical or horizontal be away, so max(dx, dy) == 1.
Predicate<Mine> isAdjacent = mine ->
Math.max(
Math.abs(mine.getX() - x)
Math.abs(mine.getY() - y)
) == 1;
return (int) Arrays.stream(arsenal.getArsenal())
.filter(isAdjacent)
.count();
i am trying to detect a jokerStraightFlush when analysing a poker hand.
I need to add a feature where this hand works 8S JK 6S JK 4S. The JK are jokers. I am using the exact same code logic as https://www.codeproject.com/Articles/38821/Make-a-poker-hand-evalutator-in-Java.
cardsTable represents the distribution of the Card ranks present in the hand. Each element of this array represents the amount of card of that rank(from 1 to 13, 1 being ace) present in the hand. So for 8S JK 6S JK 4S, the distribution would be
0 0 0 0 1 0 1 0 1 0 0 0 0 0
note that the position 1 is for ace (because it's simpler)
I need to find a way to detect if cardsTable[i] == 1 failed and decrement the amount of jokers used (numberOfJokers) to detect a jokerStraightFlush because in this incomplete piece of code, numberOfJokers dont decrement and i dont know how to write it in a nice way. What i do here is i check if the card at this rank exists cardsTable[i] == 1 or if its a joker... but i dont know how to check for a joker in the others consecutive rankings
I dont know if i'm clear, it's a twisted situation.. if i'm not let me know.
straight = false; // assume no straight
int numberOfJokers = 2; //(the maximum number of jokers in a hand is 2)
for (int i = 1; i <= 9; i++) { // can't have straight with lowest value of more than 10
numberOfJokers = 2 //reset the number of jokers available after each loop
if ((cardsTable[i] == 1 || numberOfJokers > 0) &&
(cardsTable[i + 1] == 1 || numberOfJokers > 0) &&
(cardsTable[i + 2] == 1 || numberOfJokers > 0) &&
(cardsTable[i + 3] == 1 || numberOfJokers > 0) &&
(cardsTable[i + 4] == 1 || numberOfJokers > 0)) {
straight = true;
break;
}
}
I also have this code but i don't know how to detect if the first condition failed so i can decrement the number of jokers remaining.
for (int i = 1; i <= 9; i++) {
numberOfJokers = 2
if (cardsTable[i] == 1 || numberOfJokers>0) {
if (cardsTable[i + 1] == 1 || numberOfJokers>0) {
if (cardsTable[i + 2] == 1 || numberOfJokers > 0) {
if (cardsTable[i + 3] == 1 || numberOfJokers > 0) {
if (cardsTable[i + 4] == 1 || numberOfJokers > 0) {
straight = true;
break;
}
}
}
}
}
}
Due to "short-circuiting" behaviour, you don't need to detect the left operand of a || resulted in true: the right operand gets evaluated if the left one was false, only.
(cardsTable[i + c] == 1 || numberOfJokers-- > 0) would work; note that
(numberOfJokers-- > 0 || cardsTable[i + c] == 1) would not.
Whether or not such code is maintainable or readable is an independent consideration, as is the quality of the overall approach to problem and solution.
Conceptually, you just need 3 of the 5 slots to be equal to 1, and the other 2 equal to 0. You could do something like this:
for (int i = 1; i <= 9; i++) {
boolean straight = true;
int numberOfJokers = 2;
for (int j = i; j <= i + 5; j++) { // I used a for loop instead of writing the statement 5 times
if (cardsTable[j] > 1) { // Can't have straight if more than one of a kind
straight = false;
}
if (cardsTable[j] == 0) {
numberOfJokers--;
}
}
if (numberOfJokers >= 0 && straight == true) {
break;
}
}
Alternatively, maybe a simpler way would be to add a position in the cardsTable array indicating the number of jokers.
nvm i used a function inside my conditions.. i don't know if it's the right way but it's logic to me x)
straight = false; // assume no straight
for (int i = 1; i <= 9; i++) // can't have straight with lowest value of more than 10
{
remainingJokers=jokers;
System.out.println("resetjokers : "+jokers);
if (cardsTable[i] == 1 || useJoker()) {
if (cardsTable[i + 1] == 1 || useJoker()) {
if (cardsTable[i + 2] == 1 || useJoker()) {
if (cardsTable[i + 3] == 1 || useJoker()) {
if (cardsTable[i + 4] == 1 || useJoker()) {
System.out.println("straight");
straight = true;
topStraightValue = i + 4; // 4 above bottom value
break;
}
}
}
}
}
}
}
private boolean useJoker() {
int remaining = remainingJokers;//remainingJokers is a global variable
remainingJokers--;
return remaining>0;
}
I am writing software that detects an images outline, thins it to a "single pixel" thick, then performs operations on the resulting outline. My hope is to eventually get the following:
I have written software that detects the RGBA colors, converts it to HSB, asks for a limit that sets whether a pixel is an outline or not (typically some value around 0.25, and checking the B (brightness) value), and then stores true or false in a 2-dimensional array of booleans (true is an outline, false is not). This gets me to stage 2 just fine. I am currently stuck on stage 3, and am currently attempting to achieve the following:
Here is my current code, where the outline[][] variable is the original 2d array of trues/falses (stage 2) and thinned[][] is the outline in stage 3.
public void thinOutline() {
thinned = new boolean[outline.length][outline[0].length];
for (int x = 0; x < thinned.length; x++)
for (int y = 0; y < thinned[0].length; y++) {
if (x > 0 && x < thinned.length - 1 && y > 0 && y < thinned[0].length - 1)
if (!thinned[x + 1][y] && !thinned[x - 1][y] && !thinned[x][y + 1] && !thinned[x][y - 1] && outline[x][y])
thinned[x][y] = true;
else
thinned[x][y] = false;
else
thinned[x][y] = outline[x][y];
}
}
I've devised a pretty simple solution that works well enough for my purposes. I have 3 arrays, outline[][], thinned[][], and thinIteration[][]. outline[][] and thinned[][] are both set when an image is loaded as explained in my question (stages 1 and 2). thinIteration[][] is then loaded with batches of pixels that need to be thinned and are considered as "border" pixels. The function then erases these pixels, and if it erased any pixels, it restarts the method. It continues to do this cycle until it finds no more pixels to thin.
The program knows whether or not to thin a pixel if it is itself an outline pixel, has at least 2 bordering pixels left/right/up/down and at least 2 bordering pixels diagonally, but not more than 3 left/right/up/down and diagonally (which would mean it is a contained pixel)
public void thinOutline() {
boolean didThinIteration = false;
for (int x = 1; x < originalWidth - 1; x++)
for (int y = 1; y < originalHeight - 1; y++) {
int numOfBorders = (thinned[x - 1][y] ? 1 : 0) + (thinned[x + 1][y] ? 1 : 0) + (thinned[x][y + 1] ? 1 : 0) + (thinned[x][y - 1] ? 1 : 0);
int numOfDiagonals = (thinned[x - 1][y + 1] ? 1 : 0) + (thinned[x + 1][y + 1] ? 1 : 0) + (thinned[x - 1][y - 1] ? 1 : 0) + (thinned[x + 1][y - 1] ? 1 : 0);
boolean thin = thinned[x][y] && numOfBorders > 1 && numOfBorders < 4 && numOfDiagonals > 1 && numOfDiagonals < 4;
thinIteration[x][y] = thin;
if (thin && !didThinIteration)
didThinIteration = true;
}
for (int x = 0; x < originalWidth; x++)
for (int y = 0; y < originalHeight; y++)
if (thinIteration[x][y])
thinned[x][y] = false;
if (didThinIteration)
thinOutline();
}
I'm having an issue with my very first program in Java. I'm trying to reproduce a game you play with matches, but the program never stops when it's supposed to...
When I enter numbers like 6, 3, 2, 1 or 7, 3, 2, 1 the loop should stop there but it just continues to the next turn as if nothing happened, even though the variables have the right value and should match the end conditions.
I'm pretty sure the problem lies in the while part of the main loop (at the very end) but I can't see it! It's probably something obvious, but well...
Here is the full source code (the rules of the game are below it):
import java.util.Scanner;
public class JeuDeNim {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//Starting the game
int totalMatches;
do {
System.out.println("How many matches do you want to play with? "
+ "(from 6 to 60)");
totalMatches = sc.nextInt();
}
while(totalMatches < 6 || totalMatches > 60);
int matchesPlayer1 = 0;//to keep track of
int matchesPlayer2 = 0;//the previous round
int i = 1;//to know whose turn it is
do{
//player 1
if(!(i % 2 == 0)) {//if odd number, player 1
//round 1
if(i == 1) {
do {
System.out.println("Player 1: How many matches do you "
+ "want to pick? (1, 2 or 3)");
matchesPlayer1 = sc.nextInt();
}
while(matchesPlayer1 < 1 || matchesPlayer1 > 3);
totalMatches = totalMatches - matchesPlayer1;
i++;
}
//odd round x
else {
do {
System.out.println("Player 1: How many matches do you "
+ "want to pick this turn?");
matchesPlayer1 = sc.nextInt();
if(totalMatches - matchesPlayer1 < 0) {
System.out.println("Pick a smaller number");
//totalMatches cannot be negative
} else if(matchesPlayer1 == matchesPlayer2) {
System.out.println("You cannot pick the same number "
+ "of matches as Player 2");
}
}
while(matchesPlayer1 < 1 || matchesPlayer1 > 3
|| (totalMatches - matchesPlayer1 < 0)
|| (matchesPlayer1 == matchesPlayer2));
totalMatches = totalMatches - matchesPlayer1;
if(totalMatches == 0
|| (totalMatches == 1 && matchesPlayer1 == 1)) {
System.out.println("Player 1 Wins!");
}
i++;
}
}
//player 2
else {
//round 2
if(i == 2) {
do {
System.out.println("Player 2: How many matches do you "
+ "want to pick? (1, 2 or 3)");
matchesPlayer2 = sc.nextInt();
if(matchesPlayer2 == matchesPlayer1) {
System.out.println("You cannot pick the same "
+ "number of matches as Player 2");
}
}
while(matchesPlayer2 < 1 || matchesPlayer2 > 3
|| matchesPlayer2 == matchesPlayer1);
totalMatches = totalMatches - matchesPlayer2;
i++;
}
//even round x
else {
do {
System.out.println("Player 2: How many matches do you "
+ "want to pick this turn?");
matchesPlayer2 = sc.nextInt();
if (totalMatches - matchesPlayer2 < 0) {
System.out.println("Pick a smaller number");
//totalMatches cannot be negative
} else if(matchesPlayer2 == matchesPlayer1) {
System.out.println("You cannot pick the same number "
+ "of matches as Player 1");
}
}
while(matchesPlayer2 < 1 || matchesPlayer2 > 3
|| (totalMatches - matchesPlayer2 < 0)
|| (matchesPlayer2 == matchesPlayer1));
totalMatches = totalMatches - matchesPlayer2;
if(totalMatches == 0
|| (totalMatches == 1 && matchesPlayer2 == 1)) {
System.out.println("Player 2 Wins!");
}
i++;
}
}
System.out.println("totalMatches: " + totalMatches + " "
+ "matchesPlayer1: " + matchesPlayer1 + " " + "matchesPlayer2: "
+ matchesPlayer2);//to check that everything is working. It is not...
}
while(totalMatches > 0
|| !(!(i % 2 == 0) && totalMatches == 1 && matchesPlayer1 == 1)
|| !((i % 2 == 0) && totalMatches == 1 && matchesPlayer2 == 1));
}
}
Here are the rules of the game: it's a two-player game, where the players take turns picking matches (1, 2 or 3) and cannot pick the same number of matches as the other player: if player one picks 2 matches, player two will have to pick either 1 or 3 matches. The player who cannot pick anymore matches loses the game, which means there are two end scenarios : when there are no more matches, or there is 1 but the other player picked 1 during the previous round.
Look at the conditions in your final while loop
totalMatches > 0 ||
!(!(i % 2 == 0) && totalMatches == 1 && matchesPlayer1 == 1) ||
!((i % 2 == 0) && totalMatches == 1 && matchesPlayer2 == 1));
This means the loop will repeat as long as there are any matches left, or it isn't player 1's turn with 1 match left and a pick of 1 match, or it isn't player 2's turn with 1 match left and a pick of 1 match.
This can never happen because (among other reasons), it requires i%2==0 and i%2 != 0. Switch the || to &&, and it should fix the problem. As was pointed out in the comments, you also need to reverse the player turn check, because the turn counter has already been incremented by this point.
The reason you want to use && here instead of ||, like in the other spots in your code is that your checking for a different concept. Every other time, you check the reasons why the loop should be repeated. This time, you check the reasons why the loop should end, and negate them. When in doubt, actually plug in values for the comparison, and see if it evaluates to what you think it should.
The game ends when there is no more matches left. So while(totalMatches > 0); is just enough.
Remove the unnecessary lines:
|| !(!(i % 2 == 0) && totalMatches == 1 && matchesPlayer1 == 1)
|| !((i % 2 == 0) && totalMatches == 1 && matchesPlayer2 == 1));