Skip to content

Commit

Permalink
WIP:Add chording to field
Browse files Browse the repository at this point in the history
  • Loading branch information
Zikoat committed Aug 11, 2024
1 parent 74a73ff commit 71a51f5
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 57 deletions.
91 changes: 88 additions & 3 deletions src/Field.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,83 @@ test("Chunk should get cell", () => {
});
});

test.only("We should be able to chord flag, chord open and chord open should also chord flag", () => {
const field = new Field(0.08, 1, "test1", "testSeed");

field.setCell(4, 1, { isMine: true });
field.setCell(7, 2, { isMine: true });
field.setCell(1, 3, { isMine: true });
field.setCell(8, 4, { isMine: true });
field.setCell(5, 5, { isMine: true });
field.setCell(6, 5, { isMine: true });
field.setCell(2, 6, { isMine: true });

field.open(3, 3);

const view = () => fieldViewToString(field, 1, 1, 8, 6);
assertFieldViewEquals(
view(),
`...x....
.11111x.
x10001..
.10122.x
.111xx..
.x......`,
);

// Flag chord the one on the corner to flag it
field.flag(4, 4);

assertFieldViewEquals(
view(),
`...x....
.11111x.
x10001..
.10122.x
.111Fx..
.x......`,
);

// Flag chord the 2, which will only flag the ones that are not already flagged
field.flag(5, 4);

assertFieldViewEquals(
view(),
`...x....
.11111x.
x10001..
.10122.x
.111FF..
.x......`,
);

// Chord open the next 2 which is full, so we can open the 3 cells
field.open(6, 4);

assertFieldViewEquals(
view(),
`...x....
.11111xx
x100013.
.101222x
.111FF3.
.x.....x`,
);

// chord open the 1 on the top corner, but this flags the neighbor because the count is the same as the flags
field.open(3, 0);

assertFieldViewEquals(
view(),
`...x....
.11111xx
x100013.
.101222x
.111FF3.
.x.....x`,
);
});

function cellToObject(cell: Cell): {
isFlagged: boolean;
isOpen: boolean;
Expand All @@ -346,8 +423,8 @@ function fieldViewToString(
): string {
let map = "";

for (let x = x0; x <= x1; x++) {
for (let y = y0; y <= y1; y++) {
for (let y = y0; y <= y1; y++) {
for (let x = x0; x <= x1; x++) {
const cell = field.getCell(x, y);
let character = "";
if (cell.isMine && cell.isOpen && !cell.isFlagged) character = "X";
Expand All @@ -368,5 +445,13 @@ function fieldViewToString(
}
map += "\n";
}
return map;
return map.trim();
}

function assertFieldViewEquals(got: string, want: string): void {
if (got !== want) {
expect(got, `Field is not the same.\nGot:\n${got}\n\nWant:\n${want}`).toBe(
want,
);
}
}
150 changes: 99 additions & 51 deletions src/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ export class Field extends PIXI.EventEmitter {
// freeze mode
}

public getCell(x: number, y: number) {
const cell = this.cellData.get(x, y);
return cell;
public getCell(x: number, y: number): Cell {
return this.cellData.get(x, y);
}

public open(x: number, y: number): Cell[] {
Expand All @@ -74,7 +73,37 @@ export class Field extends PIXI.EventEmitter {
if (this.pristine) this.setSafeCells(x, y);
let cell = this.getCell(x, y);

if (!this.isEligibleToOpen(x, y)) {
if (cell.isOpen) {
const neighbors = this.getNeighbors(x, y);
const flaggedNeighbors = neighbors.filter((cell) => cell.isFlagged);
const closedNeighbors = neighbors.filter((cell) => !cell.isOpen);
const closedUnflaggedNeighbors = neighbors.filter(
(cell) => !cell.isFlagged && !cell.isOpen,
);
const value = this.value(x, y);
const changedCells = [];
if (flaggedNeighbors.length === value) {
for (const closedUnflaggedNeighbor of closedUnflaggedNeighbors) {
const newOpenedCells = this.open(
closedUnflaggedNeighbor.x,
closedUnflaggedNeighbor.y,
);
changedCells.push(...newOpenedCells);
}
return changedCells;
} else if (closedNeighbors.length === value) {
for (const closedUnflaggedNeighbor of closedUnflaggedNeighbors) {
const newFlaggedCells = this.flag(
closedUnflaggedNeighbor.x,
closedUnflaggedNeighbor.y,
);
changedCells.push(...newFlaggedCells);
}
return changedCells;
}
}

if (cell.isFlagged || cell.isOpen) {
return [];
}

Expand All @@ -86,9 +115,6 @@ export class Field extends PIXI.EventEmitter {
this.setCell(x, y, { isOpen: true });
cell = this.getCell(x, y);

const openedCells = [];
openedCells.push(cell);

if (cell.isMine) {
this.score -= 100;
this.emit("cellChanged", cell);
Expand All @@ -109,30 +135,56 @@ export class Field extends PIXI.EventEmitter {
// we emit the event before doing the floodfill
this.emit("cellChanged", cell);

const openedCells = [];
openedCells.push(cell);

// floodfill
if (this.value(cell.x, cell.y) === 0) {
const neighbors = this.getNeighbors(cell.x, cell.y);
const closedNeighbors = neighbors.filter(
(neighbor: Cell) => !neighbor.isOpen && !neighbor.isFlagged,
); // filter the array, so only the closed neighbors are in it
const closedNeighbors = neighbors;
for (const neighbor of closedNeighbors) {
const openedNeighbors = this.open(neighbor.x, neighbor.y);
openedCells.push(...openedNeighbors);
const updatedNeighbor = this.getCell(neighbor.x, neighbor.y);
if (!updatedNeighbor.isOpen && !updatedNeighbor.isFlagged) {
const openedNeighbors = this.open(
updatedNeighbor.x,
updatedNeighbor.y,
);
openedCells.push(...openedNeighbors);
}
}
}

return openedCells;
}

public flag(x: number, y: number): Cell | null {
/**
* If the cell is closed, it toggles the flag.
*
* If the cell is open, chords flags.
* @returns Changed cells
*/
public flag(x: number, y: number): Cell[] {
console.log("flagging", x, y);
const cell = this.getCell(x, y);
if (!cell.isOpen) {
cell.isFlagged = !cell.isFlagged;
this.setCell(x, y, cell);
// todo remove this, we should base on return value of flag.
this.emit("cellChanged", cell);
return cell;
return [cell];
} else {
const closedNeighbors = this.getNeighbors(x, y).filter(
(cell) => !cell.isOpen,
);
if (closedNeighbors.length === this.value(x, y)) {
for (const closedNeighbor of closedNeighbors) {
if (!closedNeighbor.isFlagged) {
this.flag(closedNeighbor.x, closedNeighbor.y);
}
}
}
}
return null;
return [];
}

public getNeighbors(x: number, y: number) {
Expand All @@ -145,6 +197,36 @@ export class Field extends PIXI.EventEmitter {
return neighbors;
}

public restart() {
// todo
this.pristine = true;
// todo: delete all of the rows, update all of the cells
throw Error("not implemented");
}

public getAll(): Cell[] {
return this.cellData.getAll();
}

public value(x: number, y: number): number | null {
// returns the amount of surrounding mines
const cell = this.getCell(x, y);
// it does not make sense to request the value of a closed cell
if (cell.isOpen === false) return null;
else return this.getNeighbors(x, y).filter((cell) => cell.isMine).length;
}

public setVisibleChunk(x: number, y: number) {
this.visibleChunks.push({ x: x, y: y });
}

public loadVisibleChunks() {
for (let i = 0; i < this.visibleChunks.length; i++) {
this.showChunk(this.visibleChunks[i].x, this.visibleChunks[i].y);
}
this.visibleChunks = [];
}

private showChunk(x: number, y: number) {
const showChunk = this.getCoordinatesInChunk(x, y);
for (const cellCoords of showChunk) {
Expand Down Expand Up @@ -174,43 +256,9 @@ export class Field extends PIXI.EventEmitter {
return output;
}

public restart() {
// todo
this.pristine = true;
// todo: delete all of the rows, update all of the cells
throw Error("not implemented");
}

public getAll(): Cell[] {
return this.cellData.getAll();
}

public value(x: number, y: number): number | null {
// returns the amount of surrounding mines
const cell = this.getCell(x, y);
// it does not make sense to request the value of a closed cell
if (cell.isOpen === false) return null;
else return this.getNeighbors(x, y).filter((cell) => cell.isMine).length;
}

private isEligibleToOpen(x: number, y: number) {
private isEligibleToOpen(cell: Cell) {
// returns a bool, whether this cell can be opened
//if(this.gameOver) return false;
const cell = this.getCell(x, y);
if (cell.isFlagged) return false;
if (cell.isOpen) return false;
return true;
}

public setVisibleChunk(x: number, y: number) {
this.visibleChunks.push({ x: x, y: y });
}

public loadVisibleChunks() {
for (let i = 0; i < this.visibleChunks.length; i++) {
this.showChunk(this.visibleChunks[i].x, this.visibleChunks[i].y);
}
this.visibleChunks = [];
}

private setSafeCells(x0: number, y0: number) {
Expand All @@ -232,7 +280,7 @@ export class Field extends PIXI.EventEmitter {
}
}

private setCell(x: number, y: number, cell: Partial<Cell>) {
public setCell(x: number, y: number, cell: Partial<Cell>) {
const gottenCell = this.cellData.get(x, y);
if (cell.isMine !== undefined) gottenCell.isMine = cell.isMine;
if (cell.isFlagged !== undefined) gottenCell.isFlagged = cell.isFlagged;
Expand Down
6 changes: 3 additions & 3 deletions src/__snapshots__/Field.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Bun Snapshot v1, https://goo.gl/fbAQLP

exports[`JSON: "{\"foo\":\"hello\",\"bar\":\"world\"}" 1`] = `"{"foo":"hello","bar":"world"}"`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`JSON 1`] = `"{"foo":"hello","bar":"world"}"`;

exports[`JSON: "{"foo":"hello","bar":"world"}" 1`] = `"{"foo":"hello","bar":"world"}"`;

0 comments on commit 71a51f5

Please sign in to comment.