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 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 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 freeCellIdxs() { List 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 unfreeCellIdxs() { List 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 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 getCol(int i) { if (i < 0 || i >= this.w) throw new IndexOutOfBoundsException("No."); return new ArrayList() { 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 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 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 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(List.of(0, 1, 2, 3)) ); t.checkExpect( verySmall.freeCellIdxs(), new ArrayList(List.of(0)) ); t.checkExpect( oblong.freeCellIdxs(), new ArrayList(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(); } }