Skip to content

Commit

Permalink
Add components and have a public static scope for files
Browse files Browse the repository at this point in the history
  • Loading branch information
cigzigwon committed Oct 24, 2021
1 parent bd05c49 commit aabdda4
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 0 deletions.
14 changes: 14 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<title>DnD Mine Sweeper</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="author" content="Chris Sobeck">
<meta name="description" content="DnD Mine Sweeper">
</head>
<body class="font-sans bg-gray-100">
<div id="root">
</div>
</body>
</html>
186 changes: 186 additions & 0 deletions src/js/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import React, { useEffect, useState } from "react"
import { EyeIcon, ArrowsExpandIcon } from "@heroicons/react/outline"
import DialogConfirm from "./DialogConfirm"

import '../sass/styles.sass'

const get_random_integer = (max) => {
return Math.floor(Math.random() * max)
}

const max_mines = 8
const x_set = [0, 1, 2, 3, 4, 5]
const y_set = Array.from(x_set)
const colors = [
'green',
'blue',
'pink',
'purple',
'pink',
'indigo',
'yellow'
]
const primary_color = colors[get_random_integer(colors.length)]
const grid = Array.from(x_set).fill(Array.from(y_set).fill(0))

const mine_generator = (prev, curr) => {
const x_key = get_random_integer(x_set.length)
const y_key = get_random_integer(y_set.length)
const x = x_set[x_key]
const y = y_set[y_key]

if (prev.find(([a, b]) => a == x && b == y)) {
return prev
}

return prev.concat([[x, y]])
}

const mines = new Array(max_mines).fill().reduce(mine_generator, [])

const App = () => {
const [game_state, set_game_state] = useState({
message: "",
})
const [is_open, set_isopen] = useState(false)
const [blasts, set_blasts] = useState(0)
const [swept, set_swept] = useState(0)
const on_dragmove = event => {
const target = event.target
// keep the dragged position in the data-x/data-y attributes
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy

// translate the element
target.style.transform = 'translate(' + x + 'px, ' + y + 'px)'

// update the posiion attributes
target.setAttribute('data-x', x)
target.setAttribute('data-y', y)
}

// this function is used later in the resizing and gesture demos
window.dragMoveListener = on_dragmove

useEffect(() => {
import("interactjs").then(({ default: interact }) => {
/* The dragging code for '.draggable' from the demo above
* applies to this demo as well so it doesn't have to be repeated. */

// enable draggables to be dropped into this
interact('.dropzone').dropzone({
// only accept elements matching this CSS selector
accept: '#sweeper',
// Require a 75% element overlap for a drop to be possible
overlap: 0.75,

// listen for drop related events:

ondropactivate: function (event) {
// add active dropzone feedback
event.target.classList.add('drop-active')
},
ondragenter: function (event) {
const draggable_elem = event.relatedTarget
const dropzone_elem = event.target

// feedback the possibility of a drop
dropzone_elem.classList.add('drop-target')
dropzone_elem.classList.add(`bg-${primary_color}-600`)
draggable_elem.classList.add('can-drop')
// draggable_elem.textContent = ' '
},
ondragleave: function (event) {
// remove the drop feedback style
event.target.classList.remove('drop-target')
event.target.classList.remove(`bg-${primary_color}-600`)
event.relatedTarget.classList.remove('can-drop')
// event.target.classList.remove('dropped')
// event.relatedTarget.textContent = ' '
},
ondrop: function (event) {
const dropzone_elem = event.target


if (dropzone_elem.classList.contains('xxx')) {
dropzone_elem.innerHTML = '💣'
dropzone_elem.classList.add('bg-red-400')
dropzone_elem.classList.remove('xxx')
set_blasts(prev => prev + 1)
} else {
dropzone_elem.classList.add('bg-gray-300')
dropzone_elem.innerHTML = '✅'
}

if (dropzone_elem.classList.contains(`bg-${primary_color}-400`)) {
set_swept(prev => prev + 1)
}

dropzone_elem.classList.add('dropped')
dropzone_elem.classList.remove(`bg-${primary_color}-400`)
},
ondropdeactivate: function (event) {
// remove active dropzone feedback
event.target.classList.remove('drop-active')
event.target.classList.remove('drop-target')
event.target.classList.remove(`bg-${primary_color}-600`)
}
})

interact('.drag-drop')
.draggable({
inertia: true,
modifiers: [
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true
})
],
autoScroll: true,
// dragMoveListener from the dragging demo above
listeners: { move: dragMoveListener }
})
})
}, [])

useEffect(() => {
if (blasts >= mines.length) {
set_game_state({ message: "You Lose!!!" })
set_isopen(true)
}
}, [blasts])

useEffect(() => {
if (swept >= (x_set.length * y_set.length - 1) && blasts < mines.length) {
set_game_state({ message: "You Win!!! Congrats!!" })
set_isopen(true)
}
}, [swept])

return (
<>
<div id="sweeper" className="drag-drop h-8 w-8 bg-gray-900 flex items-center justify-center">
<EyeIcon className="h-4/5 w-4/5 text-white text-center" />
</div>
<div className="container mt-24 px-4 mx-auto w-full md:w-3/5 lg:w-1/3">
<h1 className={`mb-6 text-3xl text-${primary_color}-600`}>DnD Mine Sweeper</h1>
<p className="mb-6">Drag and drop the eye icon to uncover the booby traps (mines), you booby! There are <strong className={`font-semibold text-${primary_color}-500`}>{mines.length}</strong> of them.</p>
{grid.map((y, y_key) => (
<div key={y_key} className="flex flex-row">
{y.map((x, x_key) => {
const found = mines.find(([x, y]) => (x == x_key && y == y_key))
const classes = `dropzone inline-flex flex-grow items-center justify-center p-2 lg:p-4 m-1 bg-${primary_color}-400` + (found ? " xxx" : "")

return (
<div key={x_key} data-x={x_key} data-y={y_key} className={classes}></div>
)
})}
</div>
))}
</div>
<DialogConfirm is_open={is_open} set_isopen={set_isopen} primary_color={primary_color} {...game_state} />
</>
)
}

export default App
31 changes: 31 additions & 0 deletions src/js/DialogConfirm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from "react"
import { Dialog } from "@headlessui/react"

const DialogConfirm = ({ is_open, set_isopen, message, primary_color }) => {
return (
<Dialog
open={is_open}
onClose={() => {/* prevent */}}
className="fixed z-10 inset-0 overflow-y-auto"
>
<div className="flex items-center justify-center min-h-screen">
<Dialog.Overlay className="fixed inset-0 bg-black opacity-30" />

<div className="relative bg-white rounded w-full md:w-1/3 mx-8 md:mx-auto py-4 px-6">
<Dialog.Title className={`text-2xl text-${primary_color}-600 mb-4`}>{ message }</Dialog.Title>
<p className="mb-4">Would you like to restart the game?</p>
<div className="flex flex-shrink justify-end">
<button onClick={e => {
location.reload()
}} className={`inline-flex bg-${primary_color}-600 text-white rounded-full h-8 w-1/4 mr-4 px-4 justify-center items-center`}>Yes</button>
<button onClick={e => {
set_isopen(false)
}} className={`inline-flex bg-white text-${primary_color}-600 border-2 border-${primary_color}-600 rounded-full h-8 w-1/4 px-4 justify-center items-center`}>No</button>
</div>
</div>
</div>
</Dialog>
)
}

export default DialogConfirm
6 changes: 6 additions & 0 deletions src/js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import ReactDOM from "react-dom"
import * as React from "react"
import App from "./App"


ReactDOM.render(<App />, document.querySelector("#root"))

0 comments on commit aabdda4

Please sign in to comment.