Fucking hell here we go again.

This commit is contained in:
2025-05-12 23:02:41 -04:00
parent fd75827c7a
commit dfef396d17
2 changed files with 367 additions and 276 deletions

303
twentyfortyeight/Main2.java Normal file
View File

@@ -0,0 +1,303 @@
package twentyfortyeight;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javalib.impworld.*;
import javalib.worldimages.*;
import tester.Tester;
class ImgUtil {
static Color color(int n) {
int r = (int)Math.min(255, 20 * (int)(Math.log(n) / Math.log(2)));
return new Color(r, r / 2 + 80, r / 3 + 20);
}
// Create a tile image for a number.
static WorldImage mktile(int n) {
return new OverlayImage(
new TextImage(
String.valueOf(n), Game.SCALE / 2, FontStyle.BOLD, Color.BLACK
),
new RectangleImage(
Game.SCALE, Game.SCALE, OutlineMode.SOLID, ImgUtil.color(n)
)
);
}
// Create a fee space image.
static WorldImage mkfree() {
return new RectangleImage(
Game.SCALE, Game.SCALE, OutlineMode.OUTLINE, Color.GRAY
);
}
}
// A game of 2048.
class Game extends World {
static int SCALE = 100;
int score;
Grid grid;
Random rand;
WorldScene scene;
int w, h; // The pixel widths and heights.
// Convenience constructor for init with default settings.
Game() {
this.grid = new Grid(4, 4);
this.score = 0;
this.rand = new Random();
this.w = this.grid.w * SCALE;
this.h = this.grid.h * SCALE;
this.scene = new WorldScene(this.w, this.h);
}
// Convenience constructor for init with custom settings.
Game(int w, int h) {
this.grid = new Grid(w, h);
this.score = 0;
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);
}
void launchGame() {
this.bigBang(this.w, this.h, 0.01);
this.grid.set(0, 0, 2).set(0, 2, 2);
}
public WorldScene makeScene() {
this.scene = this.getEmptyScene();
this.draw();
return this.scene;
}
public void onKeyEvent(String key) {
if (key.equals("left") || key.equals("h")) this.grid.mv(0);
else if (key.equals("down") || key.equals("j")) this.grid.mv(1);
else if (key.equals("up") || key.equals("k")) this.grid.mv(2);
else if (key.equals("right") || key.equals("l")) this.grid.mv(3);
else return; // Don't draw or add tiles if bad key pressed.
this.draw();
List<Integer> frees = this.grid.freeCellIdxs();
// Add tiles
this.grid.buf.set(frees.get(this.rand.nextInt(frees.size())), 2);
this.draw();
}
// Draw current game state.
void draw() { this.scene = this.grid.render(); }
}
// The grid in which the game is played.
class Grid {
int w; // The width of the grid.
int h; // The height of the grid.
ArrayList<Integer> buf; // The buffer containing the tiles.
int sz; // The size of the buffer -- always w * h.
Grid(int w, int h) {
if (w < 1 || h < 1)
throw new IllegalArgumentException("Can't make grid that small.");
this.w = w;
this.h = h;
this.sz = this.w * this.h;
this.buf = new ArrayList<>(this.sz);
// Fill with zeros.
for (int i = 0; i < this.sz; this.buf.add(i++ * 0));
}
// Get the indexes of all "free" cells -- those whose value in buf is 0,
// and are represented as blank tile.
List<Integer> freeCellIdxs() {
List<Integer> free = new ArrayList<>();
for (int i = 0; i < this.sz; i++)
if (this.buf.get(i) == 0 && free.add(i)) continue;
return free;
}
// 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++)
if (this.buf.get(i) != 0 && unfree.add(i)) continue;
return unfree;
}
*/
// Set the tile at the coords.
Grid set(int x, int y, int 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); }
// Get the x coord for an index.
int ix(int i) { return i % this.h; }
// Get the y coord for an index.
int iy(int i) { return i % this.w; }
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 ith row.
List<Integer> getRow(int i) {
return this.buf.subList(
this.where(0, i), this.where(this.w - 1, i) + 1
);
}
// Get the ith col. A bit more complex.
List<Integer> getCol(int i) {
if (i < 0 || i >= this.w) throw new IndexOutOfBoundsException("No.");
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; }
};
}
// 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;
}
// 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.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;
if (n == 0) bg.placeImageXY(ImgUtil.mkfree(), coordx, coordy);
else bg.placeImageXY(ImgUtil.mktile(n), coordx, coordy);
}
return bg;
}
}
class Examples {
Grid verySmall, small, oblong, regular;
Game game;
void init() {
verySmall = new Grid(1, 1);
small = new Grid(2, 2);
oblong = new Grid(1, 3);
regular = new Grid(4, 4);
}
void testGridFreeCellIdxs(Tester t) {
init();
t.checkExpect(
small.freeCellIdxs(), new ArrayList<Integer>(List.of(0, 1, 2, 3))
);
t.checkExpect(
verySmall.freeCellIdxs(), new ArrayList<Integer>(List.of(0))
);
t.checkExpect(
oblong.freeCellIdxs(), new ArrayList<Integer>(List.of(0, 1, 2))
);
}
void testGridSet(Tester t) {
init();
small.set(0, 0, 1);
t.checkExpect(small.freeCellIdxs(), new ArrayList<>(List.of(1, 2, 3)));
small.set(1, 1, 1);
t.checkExpect(small.freeCellIdxs(), new ArrayList<>(List.of(1, 2)));
}
void testRegular(Tester t) {
init();
regular.set(1, 0, 16)
.set(2, 0, 16)
.set(3, 0, 32)
.set(0, 1, 4)
.set(1, 1, 4)
.set(4, 1, 2);
t.checkExpect(regular.get(1, 0), 16);
t.checkExpect(regular.get(0, 0), 0);
t.checkExpect(regular.get(3, 3), 0);
}
void testGame(Tester t) {
game = new Game(2, 2);
game.launchGame();
}
}

View File

@@ -2,302 +2,90 @@ package twentyfortyeight;
import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javalib.impworld.*;
import javalib.worldimages.*;
import tester.Tester;
class ImgUtil {
static Color color(int n) {
int r = (int)Math.min(255, 20 * (int)(Math.log(n) / Math.log(2)));
// Config constants.
class Util {
// Game scale.
static int scale = 24;
}
return new Color(r, r / 2 + 80, r / 3 + 20);
// The board on which the game is played.
class Board {
int sz; // The side length of the board grid square.
Map<Coord, Tile> board;
Board(int sz) {
this.sz = sz;
this.board = new HashMap<Coord, Tile>();
}
// Create a tile image for a number.
static WorldImage mktile(int n) {
return new OverlayImage(
new TextImage(
String.valueOf(n), Game.SCALE / 2, FontStyle.BOLD, Color.BLACK
),
new RectangleImage(
Game.SCALE, Game.SCALE, OutlineMode.SOLID, ImgUtil.color(n)
)
// Draw the board.
WorldImage draw() {
WorldImage img;
ArrayList<WorldImage> rows = new ArrayList<>();
// For each row.
for (int y = 0; y < this.sz; y++) {
ArrayList<WorldImage> row = new ArrayList<>();
// For each element in the row.
for (int x = 0; x < this.sz; x++)
row.add(this.board.get(new Coord(x, y)).draw());
// Add row to the list.
rows.add(row.stream().reduce(
new EmptyImage(),
(cell1, cell2) -> new BesideImage(cell1, cell2)
));
}
// Collapse rows into single image.
return rows.stream().reduce(
new EmptyImage(),
(row1, row2) -> new BesideAlignImage(AlignModeY.BOTTOM, row1, row2)
);
}
// Create a fee space image.
static WorldImage mkfree() {
return new RectangleImage(
Game.SCALE, Game.SCALE, OutlineMode.OUTLINE, Color.GRAY
);
}
}
// A moveable tile on the board.
class Tile {
int n;
// A game of 2048.
class Game extends World {
static int SCALE = 100;
Tile(int n) { this.n = n; }
int score;
Grid grid;
Random rand;
WorldScene scene;
int w, h; // The pixel widths and heights.
// Generate the color of the tile.
Color col() {
int r =
(int)Math.min(255, 20 * (int)(Math.log(this.n) / Math.log(2)));
return new Color(r, r / 2 + 80, r / 3 + 20);
}
// Convenience constructor for init with default settings.
Game() {
this.grid = new Grid(4, 4);
this.score = 0;
this.rand = new Random();
this.w = this.grid.w * SCALE;
this.h = this.grid.h * SCALE;
this.scene = new WorldScene(this.w, this.h);
}
// Convenience constructor for init with custom settings.
Game(int w, int h) {
this.grid = new Grid(w, h);
this.score = 0;
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);
}
void launchGame() {
this.bigBang(this.w, this.h, 0.01);
this.grid.set(0, 0, 2).set(0, 2, 2);
}
public WorldScene makeScene() {
this.scene = this.getEmptyScene();
this.draw();
return this.scene;
}
public void onKeyEvent(String key) {
if (key.equals("left") || key.equals("h")) this.grid.mv(0);
else if (key.equals("down") || key.equals("j")) this.grid.mv(1);
else if (key.equals("up") || key.equals("k")) this.grid.mv(2);
else if (key.equals("right") || key.equals("l")) this.grid.mv(3);
else return; // Don't draw or add tiles if bad key pressed.
this.draw();
List<Integer> frees = this.grid.freeCellIdxs();
// Add tiles
this.grid.buf.set(frees.get(this.rand.nextInt(frees.size())), 2);
this.draw();
}
// Draw current game state.
void draw() { this.scene = this.grid.render(); }
}
// The grid in which the game is played.
class Grid {
int w; // The width of the grid.
int h; // The height of the grid.
ArrayList<Integer> buf; // The buffer containing the tiles.
int sz; // The size of the buffer -- always w * h.
Grid(int w, int h) {
if (w < 1 || h < 1)
throw new IllegalArgumentException("Can't make grid that small.");
this.w = w;
this.h = h;
this.sz = this.w * this.h;
this.buf = new ArrayList<>(this.sz);
// Fill with zeros.
for (int i = 0; i < this.sz; this.buf.add(i++ * 0));
}
// Get the indexes of all "free" cells -- those whose value in buf is 0,
// and are represented as blank tile.
List<Integer> freeCellIdxs() {
List<Integer> free = new ArrayList<>();
for (int i = 0; i < this.sz; i++)
if (this.buf.get(i) == 0 && free.add(i)) continue;
return free;
}
// 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++)
if (this.buf.get(i) != 0 && unfree.add(i)) continue;
return unfree;
}
*/
// Set the tile at the coords.
Grid set(int x, int y, int 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); }
// Get the x coord for an index.
int ix(int i) { return i % this.h; }
// Get the y coord for an index.
int iy(int i) { return i % this.w; }
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));
// Draw the tile.
WorldImage draw() {
return new RectangleImage(
Util.scale, Util.scale, OutlineMode.SOLID, this.col()
);
}
}
// Get the ith row.
List<Integer> getRow(int i) {
return this.buf.subList(
this.where(0, i), this.where(this.w - 1, i) + 1
);
}
// Board coordinates.
class Coord {
int x, y;
// Get the ith col. A bit more complex.
List<Integer> getCol(int i) {
if (i < 0 || i >= this.w) throw new IndexOutOfBoundsException("No.");
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; }
};
}
// 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++;
Coord(int x, int y) {
this.x = x;
this.y = y;
}
// If nothing has been done, give up.
if (c == ln.size() - 1) return false;
return true;
}
// 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);
}
public boolean equals(Object that) {
if (!(that instanceof Coord)) return false;
else return ((Coord)that).x == this.x && ((Coord)that).y == this.y;
}
public int hashCode() { return 31 * Integer.hashCode(this.x + this.y); }
}
// Render the grid.
WorldScene render() {
WorldScene bg =
new WorldScene(Game.SCALE * this.w, Game.SCALE * this.h);
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;
if (n == 0) bg.placeImageXY(ImgUtil.mkfree(), coordx, coordy);
else bg.placeImageXY(ImgUtil.mktile(n), coordx, coordy);
}
return bg;
}
}
class Examples {
Grid verySmall, small, oblong, regular;
Game game;
void init() {
verySmall = new Grid(1, 1);
small = new Grid(2, 2);
oblong = new Grid(1, 3);
regular = new Grid(4, 4);
}
void testGridFreeCellIdxs(Tester t) {
init();
t.checkExpect(
small.freeCellIdxs(), new ArrayList<Integer>(List.of(0, 1, 2, 3))
);
t.checkExpect(
verySmall.freeCellIdxs(), new ArrayList<Integer>(List.of(0))
);
t.checkExpect(
oblong.freeCellIdxs(), new ArrayList<Integer>(List.of(0, 1, 2))
);
}
void testGridSet(Tester t) {
init();
small.set(0, 0, 1);
t.checkExpect(small.freeCellIdxs(), new ArrayList<>(List.of(1, 2, 3)));
small.set(1, 1, 1);
t.checkExpect(small.freeCellIdxs(), new ArrayList<>(List.of(1, 2)));
}
void testRegular(Tester t) {
init();
regular.set(1, 0, 16)
.set(2, 0, 16)
.set(3, 0, 32)
.set(0, 1, 4)
.set(1, 1, 4)
.set(4, 1, 2);
t.checkExpect(regular.get(1, 0), 16);
t.checkExpect(regular.get(0, 0), 0);
t.checkExpect(regular.get(3, 3), 0);
}
void testGame(Tester t) {
game = new Game(2, 2);
game.launchGame();
}
}