One of my favorite board games is Hey, That's My Fish! It's a really cute game where color-coded teams of penguins slide around a hexagon-based ice field. Each piece of ice has 1 2 or 3 fish, and every time a penguin exits a piece the penguin claims the fish, but the piece melts, so the board is ever changing, and there's a lot of strategy in trying to block other penguins with melted ice and penguin bodies.
It's a strategy game, but not TOO think-y, or too much secret information to keep track of, which is a good balance for me.
There's an excellent version for iOS (it's ideal as an iPad party game) and Android, but also a free online version at yucata.de. The latter is at a really nice and congenial site (the Germans love their board games) but the interface is a little clunky for Zoom/video conference play... it's a bit geared at remote players taking turns at their leisure, and the whole site is a bit Web 1.5 feeling and requires accounts to be set up, so I'm not sure if I feel comfortable recommending it for casual work get togethers.
(From a UX standpoint, one really nice feature at the Yucata version is it uses colors to highlight the last move each player made... great when a game is going fairly slowly or people aren't being 100% attentive)
I'm not sure if it's worth coding my own version of the game (like I did for the NSFW card game Joking Hazard ) especially since it only goes up to 4 players. (If it went up to 6 or 8 it would be perfect for work game night...) But, the idea of teaching a computer to do a hex grid was interesting! Many strategy games I grew up with used hexes, (I especially remember Steve Jackson's OGRE)... there's more symmetry in the 6 directions (vs square grids, where diagonal moves go a lot further than the cardinal directions)x
As my friend Jon pointed out, a hex grid can kind of be represented as a plain old square grid, with every other row offset, but still since each hex has 6 neighbors and each square grid has 8, it was worth sketching things out and trying to get a sense of how things flowed when you treated a hex grid in terms of x,y coordinates...
I do love Apple Notes App for this kind of thing! |
Interesting - every hex has East/West and North/South neighbors, but when coerced to a square grid, like... G has neighbors to the NE and SE (namely C and M) but M, one row down, has NW and SW. Every other row, then, acts a bit differently (which is why it seemed like mod arithmetic might do the trick, as we'll see later on.
I don't have super-strong spatial intuition, so I decided to just start coding incrementally rather than planning everything out. And I decided to code things up in P5, using its convenient editor.
My first program just drew a hex grid. To be frank I just dabbled numbers until things looked right.
I started using a convention of "i,j" for the hex location, and "x,y" for the coordinate on screen. Empirically I found out if "BASESZ" was the base width... every other row had to be offset, hence
x = (i + (j % 2) / 2) * BASESZ
and the y was packed a little tighter, based on how I was drawing hexes:
(I didn't bother to check if these were "perfect" hexes, frankly if they're a little squashed it's fine)
For my second program, I started using a new data structure, just a flat list of all hexes... not quite object oriented per se, but each hex knew where it was located and could draw itself. I also draw a circle inside each hex... it was easier to do a simple pythagorean dist() to see if the mouse was over the thing than any fancier math. On every mouse move, I reset each hex's "on" key to false, and then for each hex if it's in the distance it gets set to true.
Hex Experiment 3 finally gets a little interesting. Penguins in "Hey, That's My Fish!" move a bit like a queen or rook in chess, as far as they want in any of the 6 directions (but can be blocked by missing hexes or other penguins) so I thought I'd show what moves were available from whatever hex the mouse was on:
i -=((j+1)%2);
where j is the current row. By making it visual, it was pretty quick to figure out what the math should be - a crude but effective way of getting it right.
Finally Hex Experiment 4 uses a slightly better syntax, letting each directional be a call to the same function with just a "incrementI, incrementJ" pair of functions. That way if I ever do make penguins, I only have to fix the loop to get blocked by other penguins or missing hexes in one function instead of 6.
For safety, here's the final code:
const BASESZ = 40; const COLCOUNT = 8; const ROWCOUNT = 8; const hexList = []; const hexGrid = []; const SHIFTLEFT = 50; const SHIFTDOWN = 100; const OFF = 0; const ON = 1; const OVER = 2; function setup() { createCanvas(400, 400); for (let j = 0; j < ROWCOUNT; j++) { hexGrid[j] = []; for (let i = 0; i < COLCOUNT; i++) { const hex = makeHexForLoc(i, j); hexList.push(hex); hexGrid[j][i] = hex; } } } function draw() { background(220); push(); translate(SHIFTLEFT, SHIFTDOWN); hexList.map((hex) => { drawHex(hex) }); pop(); noLoop(); } function makeHexForLoc(i, j) { const x = (i + (j % 2) / 2) * BASESZ; const y = (BASESZ * 0.75) * j; return { i, j, x, y, mode: OFF }; } function mouseMoved() { hexList.map((hex) => { hex.mode = OFF; }); const boardX = mouseX - SHIFTLEFT; const boardY = mouseY - SHIFTDOWN; let pickedHex = null; hexList.map((hex) => { if (dist(boardX, boardY, hex.x, hex.y) < BASESZ / 2) { pickedHex = hex; } }); if (pickedHex) { //Check Easts getOpenHexesInDir(pickedHex,()=>1,()=>0) .map(hex => hex.mode = ON); //Check Wests getOpenHexesInDir(pickedHex,()=>-1,()=>0) .map(hex => hex.mode = ON); //Check North Easts getOpenHexesInDir(pickedHex,(i,j)=>j%2,()=>-1) .map(hex => hex.mode = ON); //Check North Wests getOpenHexesInDir(pickedHex,(i,j)=>-(j+1)%2,()=>-1) .map(hex => hex.mode = ON); //Check South Easts getOpenHexesInDir(pickedHex,(i,j)=>j%2,()=>1) .map(hex => hex.mode = ON); //Check South Wests getOpenHexesInDir(pickedHex,(i,j)=>-(j+1)%2,()=>1) .map(hex => hex.mode = ON); pickedHex.mode = OVER; } loop(); } function getOpenHexesInDir(hex,iTransform,jTransform){ let { i, j } = hex; const res = []; while (i < COLCOUNT && j < ROWCOUNT && i >= 0 && j >= 0) { res.push(hexGrid[j][i]); i += iTransform(i,j); j += jTransform(i,j); } return res; } function drawHex(hex) { const { x, y, mode } = hex; const sz = BASESZ / 2; push(); translate(x, y); //sides line(-sz, -sz / 2, -sz, sz / 2); line(sz, -sz / 2, sz, sz / 2); //top line(-sz, -sz / 2, 0, -sz); line(0, -sz, sz, -sz / 2); //bottom line(-sz, sz / 2, 0, sz); line(0, sz, sz, sz / 2); fill(255); if (mode === OVER) fill(255, 128, 128); if (mode === ON) fill(128, 128, 255); circle(0, 0, sz * 1.5); pop(); }
I'm not sure if I'll ever make the online version of this game, but this was a satisfying bit of computer math to get done on a Saturday morning.
No comments:
Post a Comment