Created
August 13, 2019 04:25
-
-
Save afrontend/f1a8777d2ed03b745a02a9946b7814cb to your computer and use it in GitHub Desktop.
small game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!dohctype html> | |
<html> | |
<head lang='en'> | |
<meta charset='utf-8'> | |
<meta name='viewport' content='width=device-width'> | |
<title>This is a smallgame</title> | |
<style> | |
body { | |
font-family: 'Helvetica Neue', Arial, sans-serif; | |
color: #333; | |
font-weight: 300; | |
margin: 0; | |
text-align: top; | |
touch-action: none; | |
overflow: hidden; | |
} | |
canvas { | |
image-rendering: -moz-crisp-edges; | |
image-rendering: pixelated; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id=c></canvas> | |
<script src='bundle.js'></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const CIRCLES = 99; | |
const GRAVITY = 0.1; | |
const FRICTION = 0.7; | |
const LEFT = 37; | |
const UP = 38; | |
const RIGHT = 39; | |
const DOWN = 40; | |
const global = {}; | |
global.mouse = {}; | |
global.key = null; | |
global.prevKey = null; | |
function clone(obj) { | |
return Object.assign({}, obj); | |
} | |
function compose() { | |
var fns = arguments; | |
return function (result) { | |
for (var i = fns.length - 1; i > -1; i--) { | |
result = fns[i].call(this, result); | |
} | |
return result; | |
}; | |
}; | |
function getXRange(min) { | |
return { min: min, max: window.innerWidth - min }; | |
} | |
function getYRange(min) { | |
return { min: min, max: window.innerHeight - min }; | |
} | |
function getRandomArbitrary(min, max) { | |
return Math.floor(Math.random() * (max - min) + min); | |
} | |
function getRandomX(min) { | |
return getRandomArbitrary(min, window.innerWidth - min); | |
} | |
function getRandomY(min) { | |
return getRandomArbitrary(min, window.innerHeight - min); | |
} | |
function getRandom(max) { | |
return Math.floor(Math.random() * max); | |
} | |
function getRadians(angle) { | |
return angle * Math.PI/180; | |
} | |
function getDxDy(angle, speed) { | |
const dx = Math.cos(getRadians(angle)) * speed; | |
const dy = Math.sin(getRadians(angle)) * speed; | |
return { dx, dy }; | |
} | |
function createCircle(count) { | |
const id = count; | |
const radius = getRandomArbitrary(5, 100); | |
const x = getRandomX(radius); | |
const y = getRandomY(radius); | |
const angle = getRandom(360); | |
const speed = Math.abs(radius-100)/4; | |
const { dx, dy } = getDxDy(angle, speed); | |
const xRange = getXRange(radius); | |
const yRange = getYRange(radius); | |
return { id, radius, x, y, angle, speed, dx, dy, xRange, yRange }; | |
} | |
function isCircle(item) { | |
if (!item) return false; | |
const { x, y, radius } = item; | |
if (x && y && radius) { | |
return true; | |
} | |
return false; | |
} | |
function createPerson(count = 0) { | |
const id = count; | |
const width = 20; | |
const height = 50; | |
const x = (window.innerWidth / 2) - (width / 2); | |
const y = window.innerHeight - height; | |
const fillStyle = 'blue' | |
const xRange = getXRange(0); | |
return { id, x, y, width, height, fillStyle, xRange }; | |
} | |
function isPerson(item) { | |
if (!item) return false; | |
const { x, y, width, height, fillStyle } = item; | |
if (x && y && width && height && fillStyle === 'blue') { | |
return true; | |
} | |
return false; | |
} | |
function createRope(person) { | |
const rope = createPerson(); | |
rope.x = person.x; | |
rope.y = person.y; | |
rope.height = person.height / 2; | |
rope.yRange = getYRange(0); | |
rope.fillStyle = 'yellow'; | |
rope.id = person.id ? person.id : 0; | |
return rope; | |
} | |
function isRope(item) { | |
if (!item) return false; | |
const { x, y, width, height, fillStyle } = item; | |
if (x && y && width, height, fillStyle === 'yellow') { | |
return true; | |
} | |
return false; | |
} | |
function makeCircles(len) { | |
const circles = []; | |
let count = len; | |
while (count--) { | |
circles.push(createCircle(count)); | |
} | |
circles.push(createPerson(++len)); | |
return circles; | |
} | |
function clearScreen(ctx) { | |
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); | |
} | |
function drawCircle(ctx, circle) { | |
ctx.beginPath(); | |
ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI*2, false); | |
ctx.stroke(); | |
if (circle.fillStyle) { | |
ctx.fillStyle = circle.fillStyle; | |
} else { | |
ctx.fillStyle = "rgba(20, 100, 20, 0.1)" | |
} | |
if (circle.lineWidth) { | |
ctx.lineWidth = circle.lineWidth; | |
} | |
ctx.fill(); | |
} | |
const drawCircles = ctx => { | |
return circles => { | |
circles.forEach(function(circle) { | |
if (isCircle(circle)) { | |
drawCircle(ctx, circle) | |
} | |
}); | |
return circles; | |
} | |
} | |
const drawPerson = ctx => { | |
return circles => { | |
circles.forEach(function(item) { | |
if(isPerson(item)) { | |
ctx.fillStyle = item.fillStyle; | |
ctx.fillRect(item.x, item.y, item.width, item.height); | |
} | |
}); | |
return circles; | |
} | |
} | |
const drawRope = ctx => { | |
return circles => { | |
circles.forEach(function(item) { | |
if(isRope(item)) { | |
ctx.fillStyle = item.fillStyle; | |
ctx.fillRect(item.x, item.y, item.width, item.height); | |
} | |
}); | |
return circles; | |
} | |
} | |
function distance(x1, y1, x2, y2) { | |
return Math.floor(Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))); | |
} | |
function isOverlap(person, circle) { | |
if (distance(person.x, person.y, circle.x, circle.y) <= circle.radius || | |
distance(person.x + person.width, person.y, circle.x, circle.y) <= circle.radius || | |
distance(person.x, person.y + person.height, circle.x, circle.y) <= circle.radius || | |
distance(person.x + person.width, person.y + person.height, circle.x, circle.y) <= circle.radius | |
) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function isSomeOverlap(ropes, circle) { | |
return ropes.some(rope => isOverlap(rope, circle)); | |
} | |
function isInRange(value, range) { | |
return value ? (value >= range.min && value <= range.max) : false; | |
} | |
const applyMoveFreely = compose( | |
applyMoveLeftOrRight, | |
applyMoveUpOrDown | |
); | |
function applyMoveLeftOrRight(circle) { | |
const c = clone(circle); | |
if (c.dx) { | |
if (isInRange(c.x + c.dx, c.xRange)) { | |
c.x += c.dx; | |
} else { | |
c.angle = 180 - c.angle; | |
const { dx, dy } = getDxDy(c.angle, c.speed); | |
c.dx = dx; | |
c.dy = dy; | |
// c.dx = -c.dx; | |
} | |
} | |
return c; | |
} | |
function applyMoveUpOrDown(circle) { | |
const c = clone(circle); | |
if (c.dy) { | |
if (isInRange(c.y + c.dy, c.yRange)) { | |
c.y += c.dy; | |
} else { | |
c.angle = 360 - c.angle; | |
const { dx, dy } = getDxDy(c.angle, c.speed); | |
c.dx = dx; | |
c.dy = dy; | |
// c.dy = -c.dy; | |
} | |
} | |
return c; | |
} | |
function applyGravity(circle) { | |
const c = clone(circle); | |
if (!isInRange(c.y + c.dy, c.yRange)) { | |
c.dy = -c.dy * FRICTION; | |
} else { | |
c.dy += GRAVITY; | |
} | |
c.y += c.dy; | |
return c; | |
} | |
function movePerson(person) { | |
const p = clone(person); | |
if (global.key === RIGHT) { | |
if (isInRange(p.x + 3, p.xRange)) { | |
p.x += 3; | |
} | |
} else if (global.key === LEFT) { | |
if (isInRange(p.x - 3, p.xRange)) { | |
p.x -= 3; | |
} | |
} | |
return p; | |
} | |
function moveRope(rope) { | |
const r = clone(rope); | |
r.y -= 10; | |
return r; | |
} | |
function isRed(item) { | |
return item && item.fillStyle === 'red'; | |
} | |
const checkRope = rope => ( | |
isInRange(rope.y, rope.yRange) ? moveRope(rope) : {} | |
); | |
const applyStyle = filter => styleCb => circles => { | |
return circles.map(circle => | |
filter(circle) ? styleCb(circle) : circle | |
); | |
} | |
const not = f => { | |
return (...args) => !f.apply(null, args); | |
} | |
const gravity = applyStyle(isRed)(applyGravity); | |
const moveLeftOrRight = applyStyle(isRed)(applyMoveLeftOrRight); | |
const moveFreely = applyStyle(not(isRed))(applyMoveFreely); | |
const updatePerson = applyStyle(isPerson)(movePerson); | |
const updateRope = applyStyle(isRope)(checkRope); | |
function findPerson(circles) { | |
return circles.find(item => { | |
return isPerson(item); | |
}); | |
} | |
function addRope(circles) { | |
if (global.key === UP) { | |
const p = findPerson(circles); | |
if (p) { | |
p.id = circles.length; | |
circles.push(createRope(p)); | |
} | |
if (global.prevKey === LEFT || global.prevKey === RIGHT) { | |
global.key = global.prevKey; | |
} | |
} | |
return circles; | |
} | |
function checkOverlapPersonItem(circles, changeItem) { | |
const p = circles.find(item => isPerson(item)); | |
if (p) { | |
return circles.map(function(item) { | |
return isOverlap(p, item) ? changeItem(item) : item; | |
}); | |
} | |
return circles; | |
} | |
function checkCollisionItem(circles, changeItem) { | |
const ropes = circles.filter(item => isRope(item)); | |
if (Array.isArray(ropes) && ropes.length > 0) { | |
return circles.map(item => { | |
return isSomeOverlap(ropes, item) ? changeItem(item) : item; | |
}); | |
} | |
return circles; | |
} | |
const changeColor = color => item => { | |
const newItem = clone(item); | |
newItem.fillStyle = color; | |
return newItem; | |
} | |
const changeRadius = limit => fn => item => { | |
const newItem = clone(item); | |
if (newItem.radius > limit) { | |
newItem.radius = fn(newItem.radius); | |
} | |
return newItem; | |
} | |
const changeHalfSize = changeRadius(10)(x => x / 2); | |
const checkPerson = circles => { | |
return checkOverlapPersonItem(circles, () => ({})); | |
}; | |
const checkCollision = (circles) => { | |
return checkCollisionItem(circles, changeColor('red')); | |
} | |
const halfSize = (circles) => { | |
return checkCollisionItem(circles, changeHalfSize); | |
} | |
const cloneCircle = (circles) => { | |
let addedCircles = []; | |
let newCircles = checkCollisionItem(circles, (item) => { | |
if (item.fillStyle !== 'red') { | |
const leftCircle = clone(item); | |
leftCircle.x -= leftCircle.radius*2; | |
leftCircle.dx = item.dx > 0 ? -leftCircle.dx : leftCircle.dx; | |
leftCircle.y -= leftCircle.radius*2; | |
leftCircle.fillStyle = 'red'; | |
addedCircles.push(leftCircle); | |
const rightCircle = clone(item); | |
rightCircle.x += rightCircle.radius*2; | |
rightCircle.dx = item.dx > 0 ? rightCircle.dx : -rightCircle.dx; | |
rightCircle.y -= rightCircle.radius*2; | |
rightCircle.fillStyle = 'red'; | |
addedCircles.push(rightCircle); | |
return {}; | |
} else { | |
return item; | |
} | |
}); | |
return newCircles.concat(addedCircles); | |
} | |
function isBottom(item) { | |
if ((item.y + item.radius) >= window.innerHeight) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function isTop(item) { | |
if (item.y <= item.radius) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function countDown(item) { | |
if (!item) return item; | |
const newItem = clone(item); | |
if (newItem.timeoutCount === undefined) { | |
newItem.timeoutCount = 100; | |
} | |
newItem.timeoutCount--; | |
console.log(newItem.timeoutCount); | |
return newItem.timeoutCount === 0 ? {} : newItem; | |
} | |
const isCircleAndRed = item => (isCircle(item) && isRed(item)); | |
const checkTopOrBottom = item => (isBottom(item) || isTop(item)) ? countDown(item) : item; | |
const checkItemOnTheBottom = applyStyle(isCircleAndRed)(checkTopOrBottom); | |
const checkTimeout = checkItemOnTheBottom; | |
function startAnimation(ctx) { | |
let circles = makeCircles(CIRCLES); | |
const update = compose( | |
moveLeftOrRight, | |
gravity, | |
moveFreely, | |
updatePerson, | |
addRope, | |
updateRope, | |
checkPerson, | |
checkCollision, | |
cloneCircle, | |
checkTimeout | |
); | |
const draw = compose( | |
drawCircles(ctx), | |
drawPerson(ctx), | |
drawRope(ctx) | |
); | |
function animate() { | |
requestAnimationFrame(animate); | |
circles = update(circles); | |
clearScreen(ctx); | |
circles = draw(circles); | |
} | |
animate(); | |
} | |
function processKeyEvent(e) { | |
global.prevKey = global.key; | |
global.key = e.keyCode; | |
const letterPressed = String.fromCharCode(global.key) | |
console.log(global.key, letterPressed.toLowerCase()); | |
} | |
function processMouseEvent(e) { | |
global.mouse = {x: e.x, y: e.y}; | |
console.log(global.mouse); | |
} | |
function activate() { | |
const c = document.querySelector("canvas"); | |
c.width = window.innerWidth; | |
c.height = window.innerHeight; | |
const ctx = c.getContext("2d"); | |
startAnimation(ctx); | |
} | |
window.addEventListener('load', activate); | |
window.addEventListener('keydown', processKeyEvent, true); | |
window.addEventListener('mousemove', processMouseEvent, true); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment