import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.Random; /** * TinyCraft - a tiny Minecraft-like 2D block demo (clean-room). * - Single-file Java Swing program * - Arrow keys/WASD to pan camera * - Mouse left-click to remove block, right-click to place * - Number keys 1-4 to pick block type * * Compile: javac TinyCraft.java * Run: java TinyCraft */ public class TinyCraft extends JPanel implements MouseListener, MouseMotionListener, KeyListener, Runnable { // World config static final int WORLD_W = 200; static final int WORLD_H = 64; static final int BLOCK = 24; // pixel block size static final int SCREEN_W = 960; static final int SCREEN_H = 640; // Block types static final int AIR = 0; static final int DIRT = 1; static final int GRASS = 2; static final int STONE = 3; static final int WATER = 4; int[][] world = new int[WORLD_W][WORLD_H]; int camX = 0, camY = 0; // camera offset in pixels int selectedBlock = DIRT; // Input boolean[] keys = new boolean[256]; int mouseX, mouseY; boolean dragging = false; public TinyCraft() { setPreferredSize(new Dimension(SCREEN_W, SCREEN_H)); setFocusable(true); addMouseListener(this); addMouseMotionListener(this); addKeyListener(this); generateWorld(); } // Simple smooth terrain generator (seeded) void generateWorld() { Random r = new Random(12345); double[] heights = new double[WORLD_W]; double current = WORLD_H * 0.4; for (int x = 0; x < WORLD_W; x++) { // small random walk, smoothed current += (r.nextDouble() - 0.5) * 3.0; // add a slow large wave for variety current += Math.sin(x * 0.05) * 2.0; current = Math.max(WORLD_H*0.15, Math.min(WORLD_H*0.85, current)); heights[x] = current; } // Fill world columns for (int x = 0; x < WORLD_W; x++) { int h = (int)Math.round(heights[x]); for (int y = 0; y < WORLD_H; y++) { if (y < h - 4) world[x][y] = STONE; else if (y < h - 1) world[x][y] = DIRT; else if (y == h - 1) world[x][y] = GRASS; else world[x][y] = AIR; } } // Make a little lake int lakeStart = WORLD_W/2 - 8; for (int x = lakeStart; x < lakeStart + 16; x++) { for (int y = WORLD_H/2; y < WORLD_H/2 + 6; y++) { if (y >= 0 && y < WORLD_H && x >= 0 && x < WORLD_W) world[x][y] = WATER; } } } // Convert screen pixel to world block coordinate Point screenToBlock(int sx, int sy) { int wx = (sx + camX) / BLOCK; int wy = (sy + camY) / BLOCK; return new Point(wx, wy); } // Place block safely void placeBlock(int bx, int by, int type) { if (bx < 0 || bx >= WORLD_W || by < 0 || by >= WORLD_H) return; // don't place into water/air weirdness: allow replacing air/water only if (world[bx][by] == AIR || world[bx][by] == WATER) { world[bx][by] = type; } } // Remove block void removeBlock(int bx, int by) { if (bx < 0 || bx >= WORLD_W || by < 0 || by >= WORLD_H) return; world[bx][by] = AIR; } @Override protected void paintComponent(Graphics g0) { super.paintComponent(g0); Graphics2D g = (Graphics2D) g0; // sky background g.setColor(new Color(120, 180, 255)); g.fillRect(0, 0, getWidth(), getHeight()); // draw blocks int startX = Math.max(0, camX / BLOCK); int endX = Math.min(WORLD_W - 1, (camX + getWidth()) / BLOCK + 1); int startY = Math.max(0, camY / BLOCK); int endY = Math.min(WORLD_H - 1, (camY + getHeight()) / BLOCK + 1); for (int x = startX; x <= endX; x++) { for (int y = startY; y <= endY; y++) { int sx = x * BLOCK - camX; int sy = y * BLOCK - camY; int t = world[x][y]; if (t == AIR) continue; switch (t) { case DIRT: g.setColor(new Color(120, 80, 40)); break; case GRASS: g.setColor(new Color(90, 140, 60)); break; case STONE: g.setColor(new Color(100, 100, 100)); break; case WATER: g.setColor(new Color(60,120,200,220)); break; default: g.setColor(Color.MAGENTA); break; } g.fillRect(sx, sy, BLOCK, BLOCK); // simple edge shading g.setColor(new Color(0,0,0,40)); g.drawRect(sx, sy, BLOCK, BLOCK); } } // HUD: selected block int hudX = 10, hudY = 10; g.setColor(new Color(0,0,0,140)); g.fillRoundRect(hudX - 6, hudY - 6, 150, 60, 8, 8); g.setColor(Color.WHITE); g.drawString("Selected (1-4): " + blockName(selectedBlock), hudX, hudY + 12); // drawing sample block int sampleX = hudX; int sampleY = hudY + 20; g.setColor(blockColor(selectedBlock)); g.fillRect(sampleX, sampleY, BLOCK/2, BLOCK/2); g.setColor(Color.WHITE); g.drawRect(sampleX, sampleY, BLOCK/2, BLOCK/2); // mini-controls hint g.setColor(Color.WHITE); g.drawString("WASD / Arrows: move camera Left click: break Right click: place", 12, getHeight() - 12); } Color blockColor(int t) { switch (t) { case DIRT: return new Color(120,80,40); case GRASS: return new Color(90,140,60); case STONE: return new Color(100,100,100); case WATER: return new Color(60,120,200); default: return Color.MAGENTA; } } String blockName(int t) { switch (t) { case DIRT: return "Dirt"; case GRASS: return "Grass"; case STONE: return "Stone"; case WATER: return "Water"; default: return "Unknown"; } } // Main loop @Override public void run() { long last = System.nanoTime(); while (true) { long now = System.nanoTime(); double dt = (now - last) / 1e9; last = now; update(dt); repaint(); try { Thread.sleep(16); } catch (InterruptedException e) {} } } void update(double dt) { int speed = 300; // pixels per second if (isKeyDown(KeyEvent.VK_A) || isKeyDown(KeyEvent.VK_LEFT)) camX -= speed * dt; if (isKeyDown(KeyEvent.VK_D) || isKeyDown(KeyEvent.VK_RIGHT)) camX += speed * dt; if (isKeyDown(KeyEvent.VK_W) || isKeyDown(KeyEvent.VK_UP)) camY -= speed * dt; if (isKeyDown(KeyEvent.VK_S) || isKeyDown(KeyEvent.VK_DOWN)) camY += speed * dt; // clamp camera camX = Math.max(0, Math.min(camX, WORLD_W * BLOCK - getWidth())); camY = Math.max(0, Math.min(camY, WORLD_H * BLOCK - getHeight())); } boolean isKeyDown(int code) { return code >= 0 && code < keys.length && keys[code]; } // Mouse events @Override public void mouseClicked(MouseEvent e) { // handled in pressed/released for button specifics } @Override public void mousePressed(MouseEvent e) { requestFocusInWindow(); Point b = screenToBlock(e.getX(), e.getY()); if (SwingUtilities.isLeftMouseButton(e)) { removeBlock(b.x, b.y); } else if (SwingUtilities.isRightMouseButton(e)) { placeBlock(b.x, b.y, selectedBlock); } dragging = true; mouseX = e.getX(); mouseY = e.getY(); } @Override public void mouseReleased(MouseEvent e) { dragging = false; } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseDragged(MouseEvent e) { // click-and-drag to continuously break/place mouseX = e.getX(); mouseY = e.getY(); Point b = screenToBlock(e.getX(), e.getY()); if (SwingUtilities.isLeftMouseButton(e)) removeBlock(b.x, b.y); else if (SwingUtilities.isRightMouseButton(e)) placeBlock(b.x, b.y, selectedBlock); } @Override public void mouseMoved(MouseEvent e) { mouseX = e.getX(); mouseY = e.getY(); } // Keyboard @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { int c = e.getKeyCode(); if (c >= 0 && c < keys.length) keys[c] = true; // number keys to switch block char ch = e.getKeyChar(); if (ch == '1') selectedBlock = DIRT; if (ch == '2') selectedBlock = GRASS; if (ch == '3') selectedBlock = STONE; if (ch == '4') selectedBlock = WATER; } @Override public void keyReleased(KeyEvent e) { int c = e.getKeyCode(); if (c >= 0 && c < keys.length) keys[c] = false; } // app launcher public static void main(String[] args) { JFrame frame = new JFrame("TinyCraft - small Java block demo"); TinyCraft tc = new TinyCraft(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(tc); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); Thread t = new Thread(tc); t.setDaemon(true); t.start(); } }