Saturday, June 24, 2017

3D Breakout in ~120 lines

Controls are WASD; game appears after the jump!

Tuesday, June 20, 2017

Choosing hobby projects

I recently read a Derek Yu article, Finishing a Game (Derek designed Spelunky), and one piece of advice has been surprisingly applicable to how I think about hobby projects:
I’ve found that there are three types of games that pique my interest: games I want to make, games I want to have made, and games I’m good at making.
Games I want to make are games where the process itself seems really fun. Maybe the mechanic seems really fun to experiment with, or maybe there’s a character I really want to animate.
Games I want to have made are games where I’m more interested in the result than in getting there. Maybe it’s a “no-limits” concept (“OMG, GTA meets Final Fantasy meets Starcraft meets…”) or just a neat idea that’s not necessarily any fun to implement.
Games I’m good at making are games that are suited to my personality and which I have experience in making. Perhaps there’s a certain genre that you naturally gravitate towards and which you understand the rhythm and flow of very well.
In my opinion, the ideas with the most potential (to be finished, at least) fall into all three categories and also satisfy the requirement “I have the time and resources to actually make this”.
Aside: I've had an interesting relationship with hobby projects (computer games / programs, board games, math projects, writing projects, etc.) over the years. The earliest attitude I remember was "It's really important for me to finish this project"; I used to feel really bad when I didn't finish projects. Fairly recently, I semi-decided to stop feeling bad about this, which was probably possible because I'd decided that my impact on the world was going to come from my job, and so the relieved pressure on hobby projects made it possible to not worry about whether I finish hobby projects. Sometimes I say "my hobby is starting projects", and I'm a lot more satisfied with this approach.

However, it's still unsettling to me sometimes to unexpectedly realize that I probably won't finish a project. Derek's trifecta let me put labels on common causes:
  1. I no longer want to work on the project: specifically, the project enters a phase that I'm actually not interested in. For example, in board games, that's creating a lot of art or separate components (like cards) and playtesting; in computer games, that's creating art, animation, levels, etc.; in programming, it's most often a stage where I find out that the appropriate tool is a complex library that will feel like working at Google to use (i.e. instead of writing programs from scratch, I spend my time decoding bad documentation). This is the most common reason for me not finishing a project.
  2. I no longer want to have finished this project: my aesthetic for projects fluctuates a lot, so sometimes I wake up and no longer see the appeal of a project. I usually find that if I just want a day or a week, it comes back, but that's not always true. This is the least common reason for me never finishing a project, but is a common delay.
  3. I'm no longer good at this project: I don't actually understand how this is different from 1. I don't really like doing things I'm not good at. Is that typical for humans?
I accidentally started applying Derek's method after reading his article, and it's increasingly affecting how I choose hobby projects; although I'm pretty chill these days about not finishing projects, I do like finishing them if I can, and in a perfect world I finish more of them. In particular, I've started seeking projects that aren't likely to run into stopping-cause 1, e.g. game-programming projects in genres that don't require much content creation. During a project, I can sometimes anticipate that a plan is likely to run into cause 1, and then make an alternate plan that avoids it. I've started to learn 3d graphics, since I think basic 3d graphics (of the kind that can be made without spending a lot of time sculpting and animating models) look better than basic 2d graphics (of the kind that can be made without spending a lot of time drawing and animating sprites).

A bigger move I've been considering is to explicitly give up on finishing board games, and just be pleasantly surprised if I do finish one. It looks to me (and I'd be surprised if pro game designers didn't agree) like basically all of board game design is playtesting, and I'm just not very interested in that. (I might find that it's not so hard to make a fun game AI to play against and playtest that way, but so far I've found it to be pretty hard.)

I'm not sure if I'll give up on finishing board games, since it does feel kind of bad. It'll be interesting to see if giving up on finishing makes me less interested in starting board games, and whether I think that's good or bad.

Overall, a surprisingly useful way of thinking -- I read a fair number of advice-style blog posts, and most of them don't turn into useful tools like this! Nice work, Derek. 

Thursday, June 15, 2017

Sunday, June 11, 2017

Tetris in <100 lines of code

Controls: W, A, S, D

0
<script>
var piece, rows = [], delay = 500, key = 0; 
onkeydown = e => key = e.key;
document.body.onload = () => (
 cvs.style.width = "100px", cvs.style.height = "300px",
 piece = makep(), frame(), setTimeout(step, delay));

// respond to player input, draw the board and piece
function frame() {
 move(({w: p => p.r = (p.r+1)%4, s: p => p.y--,
  a: p => p.x--, d: p => p.x++})[key] || (p => p));
 key = 0;
 cvs.getContext("2d").clearRect(0, 0, cvs.width, cvs.height);
 var px = (x, y) => cvs.getContext("2d").fillRect(x, cvs.height-y-1, 1, 1);
 rows.map((r, y) => Object.keys(r).map(x => px(+x, y)));
 eachblock(piece, px);
 if (delay > 0) delay -= .01;
 requestAnimationFrame(frame);
}

// try to move the piece down, clear rows and/or end the game if can't
function step() {
 setTimeout(step, delay);
 if (move(p => p.y--)) return; 
 eachblock(piece, (x, y) => (rows[y] = rows[y] || {}, rows[y][x] = 1));
 score.innerHTML = parseInt(score.innerHTML) + rows.length - 
  (rows = rows.filter(r => Object.keys(r).length < cvs.width)).length;
 if (rows.length >= cvs.height) score.innerHTML += " -- GAME OVER";
 piece = makep();
}

function move(f) {
 var newp = Object.assign({}, piece);
 f(newp);
 if (legal(newp)) {Object.assign(piece, newp); return true}
 return false;
}

var makep = () => ({y: cvs.height-1, x: cvs.width/2 -1|0, r: 0,
 blocks: ["1111", "11\n 11", " 11\n11", "11\n11",
  "111\n1", "111\n 1", "111\n  1"][Math.random()*7|0]});

function eachblock(p, f) {
 var results = [], [x, y] = [p.x, p.y];
 var r = p.blocks == "11\n11"? 0 : p.r;
 var [nextblock, nextline] = [
  [() => x++, () => {y--; x = p.x}], [() => y--, () => {x--; y = p.y}],
  [() => x--, () => {y++; x = p.x}], [() => y++, () => {x++; y = p.y}]][r];
 p.blocks.split("").map(c => {
  if (c == "\n") {nextline(); return}
  if (c == "1") results.push(f(+x + [0,1,2,0][r], +y + [0,0,-1,-1][r]));
  nextblock()});
 return results;
}

var legal = p => eachblock(p, (x, y) => x >= 0 && x < cvs.width && y >= 0
 && !(rows[y] || {})[x]).filter(x=>x).length == 4;
</script>
<canvas id="cvs" width="10" height = "30"
style = "image-rendering:pixelated; border: 1px solid black;">
</canvas> <div id="score">0</div>