Almost done. Need to add endgame check.

After grid is full (freeidx.size == 0), create copy of the grid and try
move in each direction. If nothing changes, game over.
This commit is contained in:
Jacob Signorovitch
2025-04-20 11:18:29 -04:00
parent 505fa48c36
commit 43d85b1978

View File

@@ -10,17 +10,9 @@ import tester.Tester;
class ImgUtil {
static Color color(int n) {
Double hh = Math.log(n) / Math.log(2);
int hash = Integer.hashCode(hh.intValue() << 5);
int r, g, b;
int r = (int)Math.min(255, 20 * (int)(Math.log(n) / Math.log(2)));
// Shift the hash so the least significant byte is the part you want,
// and mask to isolate that byte. Also bias towards yellow a bit (heh).
r = ((hash >> 16) & 0xff) | 0xa0; // Boost red by setting the high bit.
g = ((hash >> 8) & 0xff) | 0x90; // Boost green by setting the high bit.
b = (hash & 0xff) & 0x7f; // Reduce blue by clearing the high bit.
return new Color(r, g + 20, b);
return new Color(r, r / 2 + 80, r / 3 + 20);
}
// Create a tile image for a number.
@@ -64,10 +56,12 @@ class Game extends World {
}
// Convenience constructor for init with custom settings.
Game(int w, int h, int seed) {
Game(int w, int h) {
this.grid = new Grid(w, h);
this.score = 0;
this.rand = new Random(seed);
this.rand = new Random();
this.w = this.grid.w * SCALE;
this.h = this.grid.h * SCALE;
this.scene = new WorldScene(this.grid.w, this.grid.h);
}
@@ -93,9 +87,7 @@ class Game extends World {
List<Integer> frees = this.grid.freeCellIdxs();
// Add tiles
this.grid.buf.set(
frees.get(0), 2 // this.rand.nextInt() % (frees.size() - 1)), 2
);
this.grid.buf.set(frees.get(this.rand.nextInt(frees.size())), 2);
this.draw();
}
@@ -136,6 +128,7 @@ class Grid {
// Get the indexes of all "unfree" cells -- those whose value in buf is not
// 0, and represent a used tile.
/*
List<Integer> unfreeCellIdxs() {
List<Integer> unfree = new ArrayList<>();
for (int i = 0; i < this.sz; i++)
@@ -143,13 +136,17 @@ class Grid {
return unfree;
}
*/
// Set the tile at the coords.
Grid set(int x, int y, int v) {
this.buf.set(this.w * y + x, v);
this.buf.set(this.where(x, y), v);
return this;
}
// Get the index for the coord.
int where(int x, int y) { return this.w * y + x; }
// Get the value at the coords.
int get(int x, int y) { return this.buf.get(this.w * y + x); }
@@ -159,126 +156,88 @@ class Grid {
// Get the y coord for an index.
int iy(int i) { return i % this.w; }
// Given an index and a direction, is there a free there?
boolean freeDir(int i, int d) {
if (d == 0) { // Left.
if (i % this.w == 0) return false; // At an edge.
return this.buf.get(i - 1) == 0;
} else if (d == 1) { // Down.
if ((i + 1 + this.w) > this.sz) return false; // At an edge
return this.buf.get(i + this.w) == 0;
} else if (d == 2) { // Up.
if ((i - this.w) < 0) return false;
return this.buf.get(i - this.w) == 0;
} else if (d == 3) { // Right.
if ((i + 1) % this.w == 0) return false; // At an edge.
return this.buf.get(i + 1) ==
0; // Check if next space over is free.
}
else
throw new IllegalArgumentException("Bad direction code given.");
}
// Combine numbers that are the same into their sum. Start checking for
// combinations from the side the direction is to for each row/column.
void combine(int d) {
if (d == 0) {
for (int y = 0; y < this.h; y++) {
int prv = this.get(0, y);
for (int x = 1; x < this.w; x++) {
int cur = this.get(x, y);
if (cur == prv) {
this.set(x - 1, y, cur * 2);
this.set(x, y, 0);
}
}
}
} else if (d == 1) {
for (int x = 0; x < this.w; x++) {
int prv = this.get(x, this.h - 1);
for (int y = this.h - 2; y > 0; y--) {
int cur = this.get(x, y);
if (cur == prv) {
this.set(x, y + 1, cur * 2);
this.set(x, y, 0);
}
}
}
} else if (d == 2) {
for (int x = 0; x < this.w; x++) {
int prv = this.get(x, 0);
for (int y = 1; y < this.h; y++) {
int cur = this.get(x, y);
if (cur == prv) {
this.set(x, y - 1, cur * 2);
this.set(x, y, 0);
}
}
}
} else if (d == 3) {
for (int y = 0; y < this.h; y++) {
int prv = this.get(this.w - 1, y);
for (int x = this.w - 2; x > 0; x--) {
int cur = this.get(x, y);
if (cur == prv) {
this.set(x + 1, y, cur * 2);
this.set(x, y, 0);
}
}
}
void mv(int d) {
// For each row / col.
for (int i = 0; i < (d % 3 == 0 ? this.h : this.w); i++) {
while (this.lnMv(i, d));
this.squish(i, d);
while (this.lnMv(i, d));
}
}
// Get the indexes of all moveable tiles in a given direction -- those
// that are abutted by a free cell in the specified direction.
List<Integer> mvblDir(int d) {
List<Integer> mvbl = new ArrayList<Integer>();
for (int i : this.unfreeCellIdxs())
if (this.freeDir(i, d)) mvbl.add(i);
return mvbl;
// Get the ith row.
List<Integer> getRow(int i) {
return this.buf.subList(
this.where(0, i), this.where(this.w - 1, i) + 1
);
}
// Move the tile at the index 1 space in the specified direction.
void mvTile(int i, int d) {
System.out.println("attempting to move");
if (d == 0) this.buf.set(i - 1, this.buf.get(i));
else if (d == 1) this.buf.set(i + this.w, this.buf.get(i));
else if (d == 2) this.buf.set(i - this.w, this.buf.get(i));
else if (d == 3) this.buf.set(i + 1, this.buf.get(i));
else throw new IllegalArgumentException("Bad direction code given.");
// Get the ith col. A bit more complex.
List<Integer> getCol(int i) {
if (i < 0 || i >= this.w) throw new IndexOutOfBoundsException("No.");
this.buf.set(i, 0); // Reset original.
return new ArrayList<Integer>() {
public Integer get(int j) {
if (j < 0 || j >= h) throw new IndexOutOfBoundsException("No.");
return buf.get(w * j + i);
}
public Integer set(int j, Integer val) {
if (j < 0 || j >= h) throw new IndexOutOfBoundsException("No.");
int prv = buf.get(w * j + i);
buf.set(w * j + i, val);
return prv;
}
public int size() { return h; }
};
}
// Partial move -- apply movement rules once. There still may be tiles
// which can move after this is done. Return true if there are still
// more moves possible.
boolean partMv(int d) {
// 1. Find the tiles that can move.
List<Integer> mvbl = this.mvblDir(d);
if (mvbl.isEmpty()) return false;
// 2. Move each tile to its new location.
for (int i : mvbl) { this.mvTile(i, d); }
// Get the line.
List<Integer> getLn(int i, int d) {
return d == 0 ? this.getRow(i)
: d == 1 ? this.getCol(i).reversed()
: d == 2 ? this.getCol(i)
: this.getRow(i).reversed();
}
// Move the specified line in the specified direction once. If there is a
// combination, return false. If there are no more moves possible, return
// false.
boolean lnMv(int i, int d) {
List<Integer> ln = getLn(i, d);
int c = 0; // Moves made in line.
for (int j = 1; j < ln.size(); j++) {
if (ln.get(j - 1) == 0 && ln.get(j) != 0) {
ln.set(j - 1, ln.get(j));
ln.set(j, 0);
} else c++;
}
// If nothing has been done, give up.
if (c == ln.size() - 1) return false;
return true;
}
// Move all tiles in that direction until they can't move or combine any
// more.
void mv(int d) {
while (this.partMv(d)); // I love side effects :).
this.combine(d);
// while (this.partMv(d)); // Complete move.
// Squish like tiles together.
void squish(int i, int d) {
List<Integer> ln = getLn(i, d);
for (int j = 0; j < ln.size() - 1; j++) {
if (ln.get(j) != 0 && ln.get(j) == ln.get(j + 1)) {
ln.set(j, 2 * ln.get(j));
ln.set(j + 1, 0);
}
}
}
// Render the grid.
WorldScene render() {
WorldScene bg =
new WorldScene(Game.SCALE * this.w, Game.SCALE * this.h);
for (int i = 0; i < this.h; i++)
for (int j = 0; j < this.w; j++) {
for (int i = 0; i < this.w; i++)
for (int j = 0; j < this.h; j++) {
int n = this.get(i, j);
int coordx = i * Game.SCALE + Game.SCALE / 2;
int coordy = j * Game.SCALE + Game.SCALE / 2;
@@ -338,8 +297,7 @@ class Examples {
}
void testGame(Tester t) {
game = new Game();
game = new Game(2, 2);
game.launchGame();
}
}