diff --git a/chess/src/chess/Piece.java b/chess/src/chess/Piece.java index 82c5fd1..bc72910 100644 --- a/chess/src/chess/Piece.java +++ b/chess/src/chess/Piece.java @@ -3,7 +3,7 @@ package chess; import javalib.worldimages.*; // The piece types. -enum PieceType { PAWN, ROOK, KNIGHT, BISHOP, POPE, QUEEN, KING } +enum PieceType { PAWN, ROOK, KNIGHT, BISHOP, POPE, QUEEN, KING, SINGER } // A chess piece. abstract class Piece { @@ -14,6 +14,7 @@ abstract class Piece { this.type = type; this.col = col; } + WorldImage draw() { return new EmptyImage(); }; } diff --git a/twentyfortyeight/Main2.java b/twentyfortyeight/Main2.java new file mode 100644 index 0000000..a09aea1 --- /dev/null +++ b/twentyfortyeight/Main2.java @@ -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 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(); + } +} diff --git a/twentyfortyeight/src/twentyfortyeight/Main.java b/twentyfortyeight/src/twentyfortyeight/Main.java index 7322058..d723746 100644 --- a/twentyfortyeight/src/twentyfortyeight/Main.java +++ b/twentyfortyeight/src/twentyfortyeight/Main.java @@ -3,81 +3,79 @@ package twentyfortyeight; import java.awt.Color; import java.time.format.TextStyle; 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))); +class Examples { + void testRun(Tester t) { + Game game = new Game(); + game.board.board.put(new Coord(0, 0), new Tile(2)); + game.board.board.put(new Coord(0, 1), new Tile(20)); + game.board.board.put(new Coord(0, 2), new Tile(200)); + game.board.board.put(new Coord(0, 3), new Tile(2000)); + game.run(); + } +} - return new Color(r, r / 2 + 80, r / 3 + 20); +// Config constants. +class Util { + // Game scale. + static int scale = 256; + + // Default 2048 game board width. + static int defaultWidth = 4; + + // Calculate an appropriate font size for a number. + static double fontSize(int n) { + // Start scaling numbers down once they pass 2 digits. + double x = 0.73 * Math.pow(Math.ceil(Math.log(n) / Math.log(100)), -1); + return Util.scale * x; } - // 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 - ); + // Convert a movement to a displacement vector. + Coord moveDisp(Move move) { + if (move.equals(Move.LEFT)) return new Coord(-1, 0); + else if (move.equals(Move.DOWN)) return new Coord(0, -1); + else if (move.equals(Move.UP)) return new Coord(0, 1); + else return new Coord(0, 1); } } // A game of 2048. class Game extends World { - static int SCALE = 100; + int score; // The current score. + int sz; // The size of the grid. + int width; // The width of the image in pixels. + Board board; // The game board. - int score; - Grid grid; - Random rand; - WorldScene scene; - int w, h; // The pixel widths and heights. + Game(int score, int sz, int width, Board board) { + this.score = score; + this.sz = sz; + this.width = this.sz * Util.scale; + this.board = board; + } - // Convenience constructor for init with default settings. + // Convenince constructor with default parameters. 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); + this.sz = Util.defaultWidth; + this.width = this.sz * Util.scale; + this.board = new Board(this.sz); } public WorldScene makeScene() { - this.scene = this.getEmptyScene(); - this.draw(); - return this.scene; + WorldScene scene = new WorldScene(this.width, this.width); + scene.placeImageXY(this.board.draw(), this.width / 2, this.width / 2); + return scene; } public void onKeyEvent(String key) { +<<<<<<< HEAD int mv; if (key.equals("left") || key.equals("h")) mv = 0; else if (key.equals("down") || key.equals("j")) mv = 1; @@ -107,38 +105,45 @@ class Game extends World { 100, 20 ); } +======= + if (key.equals("left") || key.equals("h")) this.board.move(Move.LEFT); + else if (key.equals("down") || key.equals("j")) + this.board.move(Move.DOWN); + else if (key.equals("up") || key.equals("k")) this.board.move(Move.UP); + else if (key.equals("right") || key.equals("l")) + this.board.move(Move.RIGHT); + else return; + } + + void run() { this.bigBang(this.width, this.width); } +>>>>>>> origin/main } -// 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. +// The possible movements. +enum Move { UP, LEFT, RIGHT, DOWN } - Grid(int w, int h) { - if (w < 1 || h < 1) - throw new IllegalArgumentException("Can't make grid that small."); +// The board on which the game is played. +class Board { + int sz; // The side length of the board grid square. + Map board; - 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)); + Board(int sz) { + this.sz = sz; + this.board = new HashMap(); } - // 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; + // Draw the board. + WorldImage draw() { + ArrayList rows = new ArrayList<>(); - return free; - } + // For each row. + for (int y = 0; y < this.sz; y++) { + ArrayList row = new ArrayList<>(); + // For each element in the row. + for (int x = 0; x < this.sz; x++) + row.add(this.get(new Coord(x, y)).draw()); +<<<<<<< HEAD // Set the tile at the coords. Grid set(int x, int y, int v) { this.buf.set(this.where(x, y), v); @@ -168,35 +173,31 @@ class Grid { return add; } +======= + // Add row to the list. + rows.add(row.stream().reduce( + new EmptyImage(), + (cell1, cell2) -> new BesideImage(cell1, cell2) + )); + } +>>>>>>> origin/main - // Get the ith row. - List getRow(int i) { - return this.buf.subList( - this.where(0, i), this.where(this.w - 1, i) + 1 + // Collapse rows into single image. + return rows.stream().reduce( + new EmptyImage(), (row1, row2) -> new AboveImage(row1, row2) ); } - // Get the ith col. A bit more complex. - List getCol(int i) { - if (i < 0 || i >= this.w) throw new IndexOutOfBoundsException("No."); + // Get the cell at the coords. + Cell get(Coord coord) { + Cell gotten = this.board.get(coord); - 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; } - }; + // Check if there's something there, otherwise return empty space. + if (gotten == null) return new Space(); + else return gotten; } +<<<<<<< HEAD // Get the line. List getLn(int i, int d) { return d == 0 ? this.getRow(i) @@ -234,46 +235,53 @@ class Grid { ln.set(j, 2 * ln.get(j)); ln.set(j + 1, 0); add += ln.get(j); +======= + // Move in the given direction. + void move(Move move) { + for (int x = 0; x < this.sz; x++) { + for (int y = 0; y < this.sz; y++) { + // Copy logic from old code here. + Cell cell = this.get(new Coord(x, y)); +>>>>>>> origin/main } } return add; } +} - // 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); - } +// A cell on the board. +abstract class Cell { + // Generate the color of the tile. + Color col() { return Color.PINK; } - return bg; + // Draw the cell. + WorldImage draw() { + return new RectangleImage( + Util.scale, Util.scale, OutlineMode.SOLID, this.col() + ); } } -class Examples { - Grid verySmall, small, oblong, regular; +// A moveable tile on the board. +class Tile extends Cell { + int n; - Game game; + Tile(int n) { this.n = n; } - void init() { - verySmall = new Grid(1, 1); - small = new Grid(2, 2); - oblong = new Grid(1, 3); - regular = new Grid(4, 4); + 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); } - void testGridFreeCellIdxs(Tester t) { - init(); - t.checkExpect( - small.freeCellIdxs(), new ArrayList(List.of(0, 1, 2, 3)) + WorldImage draw() { + return new OverlayImage( + new TextImage( + String.valueOf(this.n), Util.fontSize(this.n), Color.BLACK + ), + super.draw() ); +<<<<<<< HEAD t.checkExpect( verySmall.freeCellIdxs(), new ArrayList(List.of(0)) ); @@ -307,5 +315,33 @@ class Examples { void testGame(Tester t) { game = new Game(4, 4); game.launchGame(); +======= +>>>>>>> origin/main } } + +// An empty space on the board. +class Space extends Cell { + Color col() { return Color.LIGHT_GRAY; } +} + +// Board coordinates. +class Coord { + int x, y; + + Coord(int x, int y) { + this.x = x; + this.y = y; + } + + Coord add(Coord that) { return that.addHelper(this.x, this.y); } + + Coord addHelper(int x, int y) { return new Coord(this.x + x, this.y + y); } + + 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); } +}