Prechádzať zdrojové kódy

More use of functional Java to improve readability.

Tankernn 8 rokov pred
rodič
commit
be49c6f6bd

+ 83 - 115
src/main/java/eu/tankernn/mines/Mines.java

@@ -3,8 +3,13 @@ package eu.tankernn.mines;
 import java.io.FileNotFoundException;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
-import java.util.Random;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
 
 import org.lwjgl.Sys;
 import org.lwjgl.input.Keyboard;
@@ -24,12 +29,11 @@ import eu.tankernn.mines.Tile.TileState;
 
 public class Mines extends TankernnGame {
 	public static final String GAME_NAME = "Minesweeper";
-	public static final Pos[] DEFAULT_PATTERN = { new Pos(0, 1), new Pos(0, -1), new Pos(1, 0), new Pos(1, 1), new Pos(1, -1),
-			new Pos(-1, 0), new Pos(-1, -1), new Pos(-1, 1) };
-	public static Settings DEFAULT_SETTINGS = new Settings(DEFAULT_PATTERN, 9, 9, 20);
+	public static final Pos[] DEFAULT_PATTERN = { new Pos(0, 1), new Pos(0, -1), new Pos(1, 0), new Pos(1, 1),
+			new Pos(1, -1), new Pos(-1, 0), new Pos(-1, -1), new Pos(-1, 1) };
+	public static Settings DEFAULT_SETTINGS = new Settings(Arrays.asList(DEFAULT_PATTERN), 9, 9, 10);
 
 	// Utility
-	private Random rand = new Random();
 	private DecimalFormat format = new DecimalFormat("0.000 sec");
 	private GuiRenderer renderer;
 	private SettingsEditor editor;
@@ -39,7 +43,7 @@ public class Mines extends TankernnGame {
 	private Texture[] checked;
 	private GUIText timeText;
 	private int tileWidth, tileHeight;
-	
+
 	/**
 	 * Next game settings.
 	 */
@@ -51,7 +55,7 @@ public class Mines extends TankernnGame {
 
 	// Game state
 	private long startTime;
-	private Tile[][] tiles;
+	private Map<Pos, Tile> tiles;
 	private int hiddenTiles;
 	private boolean running = false;
 	private boolean justClicked = false;
@@ -76,7 +80,7 @@ public class Mines extends TankernnGame {
 		} catch (FileNotFoundException e) {
 			e.printStackTrace();
 		}
-		
+
 		setSettings(DEFAULT_SETTINGS);
 		startGame();
 	}
@@ -99,28 +103,15 @@ public class Mines extends TankernnGame {
 		running = true;
 	}
 
-	private Tile[][] generateBoard(int width, int height, int mines) {
-		if (width * height < mines) {
-			throw new IllegalArgumentException("The mines will not fit on the board.");
-		}
-
-		List<Pos> minePositions = new ArrayList<Pos>();
-		for (int i = 0; i < mines; i++) {
-			Pos p;
-			do {
-				p = new Pos(rand.nextInt(width), rand.nextInt(height));
-			} while (minePositions.contains(p));
-			minePositions.add(p);
-		}
+	private Map<Pos, Tile> generateBoard(int width, int height, int mines) {
+		assert width * height < mines;
 
-		tiles = new Tile[width][height];
+		List<Pos> minePositions = new ArrayList<Pos>(allPositions(width, height).collect(Collectors.toList()));
+		Collections.shuffle(minePositions);
+		minePositions.subList(0, minePositions.size() - mines).clear();
 
-		for (int y = 0; y < height; y++) {
-			for (int x = 0; x < width; x++) {
-				tiles[x][y] = new Tile(minePositions.contains(new Pos(x, y)), new Pos(x, y));
-			}
-		}
-		return tiles;
+		return allPositions(width, height).map(p -> new Tile(minePositions.contains(p), p))
+				.collect(Collectors.toMap(t -> t.pos, t -> t));
 	}
 
 	public void check(Tile tile) {
@@ -131,40 +122,29 @@ public class Mines extends TankernnGame {
 				calculateMinesAround(tile, settings.pattern);
 	}
 
-	public void calculateMinesAround(Tile tile, Pos[] pattern) {
+	public void calculateMinesAround(Tile tile, List<Pos> pattern) {
 		hiddenTiles--;
 
-		int minesAround = 0;
-		List<Tile> testTiles = new ArrayList<Tile>();
-
-		for (int i = 0; i < pattern.length; i++) {
-			try {
-				testTiles.add(tiles[tile.pos.x + pattern[i].x][tile.pos.y + pattern[i].y]);
-			} catch (ArrayIndexOutOfBoundsException e) {
-				continue;
-			}
-		}
+		List<Tile> testTiles = pattern.stream().map(p -> new Pos(p.x + tile.pos.x, p.y + tile.pos.y))
+				.map(p -> tiles.get(p)).filter(t -> t != null).collect(Collectors.toList());
 
-		for (Tile testTile : testTiles)
-			if (testTile.isMine)
-				minesAround++;
+		int minesAround = (int) testTiles.stream().filter(t -> t.isMine).count();
 
 		tile.setMinesAround(minesAround);
-		
+
 		// Keep checking if there are no mines around
 		if (minesAround == 0)
-			for (Tile testTile : testTiles)
-				if (testTile.getState().equals(Tile.TileState.HIDDEN))
-					calculateMinesAround(testTile, pattern);
+			testTiles.stream().filter(t -> t.getState().equals(Tile.TileState.HIDDEN))
+					.forEach(t -> calculateMinesAround(t, pattern));
+
 	}
 
 	private void lose() {
 		running = false;
 		// Expose all mines
-		for (Tile[] tArr : tiles)
-			for (Tile t : tArr)
-				if (t.isMine)
-					t.setState(TileState.EXPLODED);
+		for (Tile t : tiles.values())
+			if (t.isMine)
+				t.setState(TileState.EXPLODED);
 	}
 
 	private void win() {
@@ -173,47 +153,30 @@ public class Mines extends TankernnGame {
 
 	@Override
 	public void update() {
-		// { // Command selection
-		// String[] command = sc.nextLine().split(" ");
-		//
-		// int x = Integer.parseInt(command[1]), y =
-		// Integer.parseInt(command[2]);
-		//
-		// switch (command[0]) {
-		// case "flag":
-		// tiles[x][y].toggleFlag();
-		// break;
-		// case "check":
-		// check(tiles[x][y]);
-		// break;
-		// default:
-		// System.out.println("Unknown command.");
-		// }
-		// }
-		{ // Mouse selection
-			if (Mouse.isButtonDown(1) || Mouse.isButtonDown(0)) {
-				if (!justClicked && running) {
-					justClicked = true;
-					int x, y;
-					x = Mouse.getX() / tileWidth;
-					y = Mouse.getY() / tileHeight;
-
-					if (Mouse.isButtonDown(0)) {
-						check(tiles[x][y]);
-					} else {
-						tiles[x][y].toggleFlag();
-					}
+		if (Mouse.isButtonDown(1) || Mouse.isButtonDown(0)) {
+			if (!justClicked && running) {
+				justClicked = true;
+				Pos p = new Pos(Mouse.getX() / tileWidth, Mouse.getY() / tileHeight);
+				if (!tiles.containsKey(p)) {
+					System.out.println(p);
+					dumpMap();
+					return;
 				}
-			} else
-				justClicked = false;
-		}
-		
+				if (Mouse.isButtonDown(0)) {
+					check(tiles.get(p));
+				} else {
+					tiles.get(p).toggleFlag();
+				}
+			}
+		} else
+			justClicked = false;
+
 		// Handle keyboard
 		if (running)
 			timeText.setText(format.format(((float) (Sys.getTime() - startTime)) / Sys.getTimerResolution()));
 		else if (Keyboard.isKeyDown(Keyboard.KEY_R))
 			startGame();
-		else if (Keyboard.isKeyDown(Keyboard.KEY_E) && (editor == null || !editor.isShowing()))
+		if (Keyboard.isKeyDown(Keyboard.KEY_E) && (editor == null || !editor.isShowing()))
 			editor = new SettingsEditor(this);
 
 		if (hiddenTiles == settings.mines)
@@ -226,42 +189,33 @@ public class Mines extends TankernnGame {
 	public void render() {
 		List<GuiTexture> toRender = new ArrayList<GuiTexture>();
 
-		for (int y = 0; y < settings.boardHeight; y++) {
-			for (int x = 0; x < settings.boardWidth; x++) {
-				Tile t = tiles[x][y];
-				// Text output
-				System.out.print(t.getState().equals(TileState.CHECKED) ? Integer.toString(t.getMinesAround())
-						: t.getState().appearance);
-				// OpenGL output
-				Texture tex;
-				switch (t.getState()) {
-				case CHECKED:
-					tex = checked[t.getMinesAround()];
-					break;
-				case EXPLODED:
-					tex = exploded;
-					break;
-				case FLAGGED:
-					tex = flagged;
-					break;
-				case HIDDEN:
-					tex = hidden;
-					break;
-				default:
-					tex = hidden;
-					break;
-				}
-				Vector2f scale = new Vector2f(1f / settings.boardWidth, 1f / settings.boardHeight);
-				toRender.add(new GuiTexture(tex,
-						new Vector2f(2 * scale.x * t.pos.x + scale.x - 1, 2 * scale.y * t.pos.y + scale.y - 1), scale));
+		for (Tile t : tiles.values()) {
+			// OpenGL output
+			Texture tex;
+			switch (t.getState()) {
+			case CHECKED:
+				tex = checked[t.getMinesAround()];
+				break;
+			case EXPLODED:
+				tex = exploded;
+				break;
+			case FLAGGED:
+				tex = flagged;
+				break;
+			case HIDDEN:
+			default:
+				tex = hidden;
+				break;
 			}
-			System.out.println();
+			Vector2f scale = new Vector2f(1f / settings.boardWidth, 1f / settings.boardHeight);
+			toRender.add(new GuiTexture(tex,
+					new Vector2f(2 * scale.x * t.pos.x + scale.x - 1, 2 * scale.y * t.pos.y + scale.y - 1), scale));
 		}
-		System.out.println("-------------------");
 
 		renderer.render(toRender);
 
 		super.render();
+
 	}
 
 	@Override
@@ -269,6 +223,20 @@ public class Mines extends TankernnGame {
 		super.cleanUp();
 	}
 
+	public static Stream<Pos> allPositions(int width, int height) {
+		Stream<Integer> xs = IntStream.range(0, width).boxed();
+		return xs.flatMap(x -> IntStream.range(0, height).mapToObj(y -> new Pos(x, y)));
+	}
+
+	private void dumpMap() {
+		// Text output
+		System.out.println("Size: " + tiles.size());
+		for (Pos p : tiles.keySet()) {
+			Tile t = tiles.get(p);
+			System.out.println("Pos: " + p + " Tile: " + t);
+		}
+	}
+
 	private void updateBoardSize() {
 		tileWidth = Display.getWidth() / settings.boardWidth;
 		tileHeight = Display.getHeight() / settings.boardHeight;

+ 11 - 0
src/main/java/eu/tankernn/mines/Pos.java

@@ -7,13 +7,24 @@ public class Pos {
 		this.x = x;
 		this.y = y;
 	}
+	
+	@Override
+	public int hashCode() {
+		return x + y;
+	}
 
 	@Override
 	public boolean equals(Object obj) {
+		if (obj == this) return true;
 		if (obj instanceof Pos) {
 			Pos other = (Pos) obj;
 			return other.x == this.x && other.y == this.y;
 		} else
 			return false;
 	}
+	
+	@Override
+	public String toString() {
+		return "(" + x + ", " + y + ")";
+	}
 }

+ 4 - 2
src/main/java/eu/tankernn/mines/Settings.java

@@ -1,11 +1,13 @@
 package eu.tankernn.mines;
 
+import java.util.List;
+
 public class Settings {
 	public final int boardWidth, boardHeight, area;
-	public final Pos[] pattern;
+	public final List<Pos> pattern;
 	public final int mines;
 
-	public Settings(Pos[] pattern, int boardWidth, int boardHeight, int mines) {
+	public Settings(List<Pos> pattern, int boardWidth, int boardHeight, int mines) {
 		this.boardWidth = boardWidth;
 		this.boardHeight = boardHeight;
 		this.pattern = pattern;

+ 9 - 36
src/main/java/eu/tankernn/mines/SettingsEditor.java

@@ -1,12 +1,9 @@
 package eu.tankernn.mines;
 
 import java.awt.BorderLayout;
-import java.awt.EventQueue;
 import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
@@ -24,22 +21,6 @@ public class SettingsEditor extends JFrame {
 	private Mines gameInstance;
 	private JSpinner minesSpinner = new JSpinner(), sizeSpinner = new JSpinner();
 
-	/**
-	 * Launch the application.
-	 */
-	public static void main(String[] args) {
-		EventQueue.invokeLater(new Runnable() {
-			public void run() {
-				try {
-					SettingsEditor frame = new SettingsEditor(null);
-					frame.setVisible(true);
-				} catch (Exception e) {
-					e.printStackTrace();
-				}
-			}
-		});
-	}
-
 	/**
 	 * Create the frame.
 	 */
@@ -75,22 +56,14 @@ public class SettingsEditor extends JFrame {
 		boxes[radius][radius].setEnabled(false);
 
 		JButton save = new JButton("Save");
-		save.addActionListener(new ActionListener() {
-
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				List<Pos> pattern = new ArrayList<Pos>();
-				for (int x = 0; x < boxes.length; x++) {
-					for (int y = 0; y < boxes[x].length; y++) {
-						if (boxes[x][y].isSelected())
-							pattern.add(new Pos(x - radius, y - radius));
-					}
-				}
-				if (gameInstance != null) {
-					gameInstance.setSettings(new Settings(pattern.toArray(new Pos[pattern.size()]),
-							(int) sizeSpinner.getValue(), (int) sizeSpinner.getValue(), (int) minesSpinner.getValue()));
-					dispose();
-				}
+		save.addActionListener(e -> {
+			List<Pos> pattern = Mines.allPositions(size, size).filter(p -> boxes[p.x][p.y].isSelected())
+					.collect(Collectors.toList());
+
+			if (gameInstance != null) {
+				gameInstance.setSettings(new Settings(pattern, (int) sizeSpinner.getValue(),
+						(int) sizeSpinner.getValue(), (int) minesSpinner.getValue()));
+				dispose();
 			}
 		});
 

+ 12 - 8
src/main/java/eu/tankernn/mines/Tile.java

@@ -5,29 +5,29 @@ public class Tile {
 	private TileState state = TileState.HIDDEN;
 	private int minesAround;
 	public final Pos pos;
-	
+
 	public Tile(boolean mine, Pos pos) {
 		this.isMine = mine;
 		this.pos = pos;
 	}
-	
+
 	public void setMinesAround(int minesAround) {
 		this.minesAround = minesAround;
 		this.state = TileState.CHECKED;
 	}
-	
+
 	public int getMinesAround() {
 		return minesAround;
 	}
-	
+
 	public TileState getState() {
 		return state;
 	}
-	
+
 	public void setState(TileState state) {
 		this.state = state;
 	}
-	
+
 	public void toggleFlag() {
 		if (this.state.equals(TileState.HIDDEN))
 			this.setState(TileState.FLAGGED);
@@ -37,11 +37,15 @@ public class Tile {
 
 	public enum TileState {
 		HIDDEN('*'), CHECKED(' '), EXPLODED('X'), FLAGGED('P');
-		
+
 		public final char appearance;
-		
+
 		private TileState(char value) {
 			appearance = value;
 		}
 	}
+
+	public String toString() {
+		return state.equals(TileState.CHECKED) ? Integer.toString(getMinesAround()) : Character.toString(getState().appearance);
+	}
 }