|
|
|
@@ -1,9 +1,10 @@
|
|
|
|
|
import { Button, Table, Text, Util } from '@dzeio/components'
|
|
|
|
|
import React from 'react'
|
|
|
|
|
import css from './pokemon-shuffle.styl'
|
|
|
|
|
import { Button, Table, Text, Util, NotificationManager } from '@dzeio/components'
|
|
|
|
|
import React, { MouseEvent as ReactMouseEvent } from 'react'
|
|
|
|
|
import css from './pokemon-shuffle.module.styl'
|
|
|
|
|
|
|
|
|
|
interface Cell {
|
|
|
|
|
id: number
|
|
|
|
|
id2: number
|
|
|
|
|
horizontalCombo?: true
|
|
|
|
|
verticalCombo?: true
|
|
|
|
|
justSpawned?: true
|
|
|
|
@@ -14,7 +15,7 @@ interface States {
|
|
|
|
|
items: Array<Array<Cell | undefined>>
|
|
|
|
|
loading?: true
|
|
|
|
|
movingItem?: {x: number, y: number, cell: Cell}
|
|
|
|
|
points: number
|
|
|
|
|
damage: number
|
|
|
|
|
turn: number
|
|
|
|
|
combo: number
|
|
|
|
|
comboMax: number
|
|
|
|
@@ -29,30 +30,35 @@ export default class PokemonShuffle extends React.Component<unknown, States> {
|
|
|
|
|
|
|
|
|
|
public state: States = {
|
|
|
|
|
items: [[]],
|
|
|
|
|
points: 0,
|
|
|
|
|
damage: 0,
|
|
|
|
|
turn: 0,
|
|
|
|
|
combo: 0,
|
|
|
|
|
comboMax: 0,
|
|
|
|
|
cursorPos: {x: 0, y: 0}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async componentDidMount() {
|
|
|
|
|
await this.start()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public render = () => (
|
|
|
|
|
<main>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><Text>Tour: {this.state.turn}</Text></li>
|
|
|
|
|
<li><Text>Combo: {this.state.combo}, Max: {this.state.comboMax}</Text></li>
|
|
|
|
|
<li><Text>Points: {this.state.points}</Text></li>
|
|
|
|
|
<li><Text>Points: {this.state.damage}</Text></li>
|
|
|
|
|
</ul>
|
|
|
|
|
<Table >
|
|
|
|
|
<tbody className={`${css.table} ${this.state.loading ? css.loading : ''}`}>
|
|
|
|
|
<tbody className={Util.buildClassName(css.table, [css.loading, this.state.loading])}>
|
|
|
|
|
{this.state.items.map((row, y) => (
|
|
|
|
|
<tr key={y}>
|
|
|
|
|
{row.map((cell, x) => (
|
|
|
|
|
<td
|
|
|
|
|
key={cell?.isFalling ? n++ : x}
|
|
|
|
|
key={cell?.id2 ?? x}
|
|
|
|
|
onClick={this.onCellClick(x, y)}
|
|
|
|
|
className={css.cellParent}
|
|
|
|
|
>
|
|
|
|
|
{/* <Text>{JSON.stringify(cell)}</Text> */}
|
|
|
|
|
{cell && (
|
|
|
|
|
<Text className={Util.buildClassName(
|
|
|
|
|
css[`icon-${cell.id}`],
|
|
|
|
@@ -70,16 +76,11 @@ export default class PokemonShuffle extends React.Component<unknown, States> {
|
|
|
|
|
))}
|
|
|
|
|
</tbody>
|
|
|
|
|
</Table>
|
|
|
|
|
<Button onClick={() => this.calculate()}>Calculate!</Button>
|
|
|
|
|
<Button onClick={() => this.start()}>Start!</Button>
|
|
|
|
|
{/* <Input block type="textarea" value={JSON.stringify(this.state.items)}/> */}
|
|
|
|
|
<Button onClick={this.start}>Start!</Button>
|
|
|
|
|
{this.state.movingItem && (
|
|
|
|
|
<div style={{
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
<div className={css.hoverItem} style={{
|
|
|
|
|
left: this.state.cursorPos.x,
|
|
|
|
|
top: this.state.cursorPos.y,
|
|
|
|
|
transform: 'translate(-50%, -50%)',
|
|
|
|
|
pointerEvents: 'none'
|
|
|
|
|
top: this.state.cursorPos.y
|
|
|
|
|
}}>
|
|
|
|
|
<Text className={Util.buildClassName(css[`icon-${this.state.movingItem.cell?.id}`], css.cell)}>
|
|
|
|
|
<div></div>
|
|
|
|
@@ -90,46 +91,52 @@ export default class PokemonShuffle extends React.Component<unknown, States> {
|
|
|
|
|
TODO list:
|
|
|
|
|
</Text>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><Text>Lancement Initial sans combo possible</Text></li>
|
|
|
|
|
<li><Text>Faire que les clear ce fasse de manière Async</Text></li>
|
|
|
|
|
<li><Text>Meilleurs Animation de destruction</Text></li>
|
|
|
|
|
<li><Text>Annuler le mouvement si rien n'est claim</Text></li>
|
|
|
|
|
<li><Text>Utiliser le système de damages de Pokémon Shuffle https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_Shuffle#Damage</Text></li>
|
|
|
|
|
</ul>
|
|
|
|
|
<NotificationManager />
|
|
|
|
|
</main>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private mouveMove = (ev: MouseEvent) => {
|
|
|
|
|
this.setState({cursorPos: {
|
|
|
|
|
x: ev.pageX,
|
|
|
|
|
y: ev.pageY
|
|
|
|
|
x: ev.clientX,
|
|
|
|
|
y: ev.clientY
|
|
|
|
|
}})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private start() {
|
|
|
|
|
private start = async () => {
|
|
|
|
|
if (this.state.loading) {return}
|
|
|
|
|
this.setState({
|
|
|
|
|
await this.asyncSetState({
|
|
|
|
|
loading: true,
|
|
|
|
|
// generate datas
|
|
|
|
|
items: Array
|
|
|
|
|
.from(Array(BOARD_SIZE))
|
|
|
|
|
.map(
|
|
|
|
|
() => Array.from(Array(BOARD_SIZE))
|
|
|
|
|
.map(() => ({id: random(0, ITEM_COUNT)}))
|
|
|
|
|
.map(() => ({id: random(0, ITEM_COUNT), id2: n++}))
|
|
|
|
|
)
|
|
|
|
|
}, () => this.calculate())
|
|
|
|
|
})
|
|
|
|
|
// Quickly calculate everythings to make it look like it was perfecly generated
|
|
|
|
|
await this.calculate(true)
|
|
|
|
|
this.setState({turn: 1, damage: 0, comboMax: 0, combo: 0})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onCellClick = (x: number, y: number) => async () => {
|
|
|
|
|
// console.log(x, y)
|
|
|
|
|
private onCellClick = (x: number, y: number) => async (ev: ReactMouseEvent) => {
|
|
|
|
|
if (this.state.loading) {
|
|
|
|
|
return window.alert('Cant play while Calculating')
|
|
|
|
|
return NotificationManager.addNotification('Cant play while Calculating')
|
|
|
|
|
}
|
|
|
|
|
if (!this.state.movingItem) {
|
|
|
|
|
const cell = this.state.items[y][x]
|
|
|
|
|
if (!cell) {
|
|
|
|
|
return window.alert('Cant move nothing')
|
|
|
|
|
return NotificationManager.addNotification('Cant move nothing')
|
|
|
|
|
}
|
|
|
|
|
document.addEventListener('mousemove', this.mouveMove)
|
|
|
|
|
this.setState({movingItem: {x,y,cell}})
|
|
|
|
|
this.state.items[y][x] = undefined
|
|
|
|
|
this.mouveMove(ev.nativeEvent)
|
|
|
|
|
return
|
|
|
|
|
} else {
|
|
|
|
|
document.removeEventListener('mousemove', this.mouveMove)
|
|
|
|
@@ -150,102 +157,97 @@ export default class PokemonShuffle extends React.Component<unknown, States> {
|
|
|
|
|
(res) => this.setState(states as States, () => res())
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
private async calculate() {
|
|
|
|
|
/**
|
|
|
|
|
* Check if items has combos
|
|
|
|
|
* @returns if items were changed
|
|
|
|
|
*/
|
|
|
|
|
private async checkup(): Promise<boolean> {
|
|
|
|
|
const items = this.state.items
|
|
|
|
|
let checkupCount = 0
|
|
|
|
|
let newPoints = 0
|
|
|
|
|
for (let y = 0; y < items.length; y++) {
|
|
|
|
|
const row = items[y]
|
|
|
|
|
for (let x = 0; x < row.length; x++) {
|
|
|
|
|
const cell = row[x]
|
|
|
|
|
if (!cell) {continue}
|
|
|
|
|
const id = cell.id
|
|
|
|
|
// Checkup horizontal
|
|
|
|
|
if (!cell.horizontalCombo && !(cell.isFalling || cell.justSpawned)) {
|
|
|
|
|
let sameCount = 0
|
|
|
|
|
while((x + ++sameCount) < items.length) {
|
|
|
|
|
console.log(y + sameCount, x)
|
|
|
|
|
const tmp = row[x + sameCount]
|
|
|
|
|
if (!tmp || tmp.id !== id || tmp.isFalling || tmp.justSpawned) {break}
|
|
|
|
|
}
|
|
|
|
|
if (sameCount >= 3) {
|
|
|
|
|
checkupCount += 1
|
|
|
|
|
let len = 0
|
|
|
|
|
for (let i = x; i < (x + sameCount); i++) {
|
|
|
|
|
const tmp = items[y][i]
|
|
|
|
|
if (!tmp) {continue}
|
|
|
|
|
tmp.horizontalCombo = true
|
|
|
|
|
len++
|
|
|
|
|
}
|
|
|
|
|
newPoints += calculateScore(len, this.state.combo)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Checkup Vertical
|
|
|
|
|
if (!cell.verticalCombo && !(cell.isFalling || cell.justSpawned)) {
|
|
|
|
|
let sameCount = 0
|
|
|
|
|
while((y + ++sameCount) < items.length) {
|
|
|
|
|
// console.log(y + sameCount, x)
|
|
|
|
|
const tmp = items[y + sameCount][x]
|
|
|
|
|
if (!tmp || tmp.id !== id || tmp.isFalling || tmp.justSpawned) {break}
|
|
|
|
|
}
|
|
|
|
|
if (sameCount >= 3) {
|
|
|
|
|
checkupCount += 1
|
|
|
|
|
let len = 0
|
|
|
|
|
for (let i = y; i < (y + sameCount); i++) {
|
|
|
|
|
const tmp = items[i][x]
|
|
|
|
|
if (!tmp) {continue}
|
|
|
|
|
tmp.verticalCombo = true
|
|
|
|
|
len++
|
|
|
|
|
}
|
|
|
|
|
newPoints += calculateScore(len, this.state.combo)
|
|
|
|
|
// console.log(x, y)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If combos were found
|
|
|
|
|
if (checkupCount) {
|
|
|
|
|
const combo = this.state.combo + checkupCount
|
|
|
|
|
await this.asyncSetState({
|
|
|
|
|
items,
|
|
|
|
|
damage: this.state.damage + newPoints,
|
|
|
|
|
combo,
|
|
|
|
|
comboMax: Math.max(this.state.comboMax, combo)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return !!checkupCount
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async endTurn(state?: Partial<States>) {
|
|
|
|
|
await this.asyncSetState({...state, loading: undefined, turn: this.state.turn + 1, combo: 0})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async calculate(initial = false) {
|
|
|
|
|
// remove combos
|
|
|
|
|
const items = this.state.items.map((r) => r.map((c) => {
|
|
|
|
|
if (!c) {
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
c.horizontalCombo = undefined
|
|
|
|
|
c.verticalCombo = undefined
|
|
|
|
|
delete c.horizontalCombo
|
|
|
|
|
delete c.verticalCombo
|
|
|
|
|
return c
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
let newPoints = 0
|
|
|
|
|
|
|
|
|
|
let checkupCount = 0
|
|
|
|
|
// Checkup horizontal
|
|
|
|
|
for (let y = 0; y < items.length; y++) {
|
|
|
|
|
const row = items[y]
|
|
|
|
|
for (let x = 0; x < row.length; x++) {
|
|
|
|
|
const cell = row[x]
|
|
|
|
|
if (!cell || cell.horizontalCombo) {continue}
|
|
|
|
|
const id = cell.id
|
|
|
|
|
let sameCount = 0
|
|
|
|
|
while((x + ++sameCount) < items.length) {
|
|
|
|
|
console.log(y + sameCount, x)
|
|
|
|
|
const tmp = row[x + sameCount]
|
|
|
|
|
if (!tmp || tmp.id !== id) {break}
|
|
|
|
|
}
|
|
|
|
|
if (sameCount >= 3) {
|
|
|
|
|
checkupCount += 1
|
|
|
|
|
for (let i = x; i < (x + sameCount); i++) {
|
|
|
|
|
const tmp = items[y][i]
|
|
|
|
|
if (!tmp) {continue}
|
|
|
|
|
tmp.horizontalCombo = true
|
|
|
|
|
newPoints++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check vertical
|
|
|
|
|
for (let y = 0; y < items.length; y++) {
|
|
|
|
|
const row = items[y]
|
|
|
|
|
for (let x = 0; x < row.length; x++) {
|
|
|
|
|
const cell = row[x]
|
|
|
|
|
if (!cell || cell.verticalCombo) {continue}
|
|
|
|
|
const id = cell.id
|
|
|
|
|
let sameCount = 0
|
|
|
|
|
while((y + ++sameCount) < items.length) {
|
|
|
|
|
// console.log(y + sameCount, x)
|
|
|
|
|
const tmp = items[y + sameCount][x]
|
|
|
|
|
if (!tmp || tmp.id !== id) {break}
|
|
|
|
|
}
|
|
|
|
|
// if ((y + sameCount) > items.length) {
|
|
|
|
|
// sameCount++
|
|
|
|
|
// }
|
|
|
|
|
if (sameCount >= 3) {
|
|
|
|
|
checkupCount += 1
|
|
|
|
|
for (let i = y; i < (y + sameCount); i++) {
|
|
|
|
|
const tmp = items[i][x]
|
|
|
|
|
if (!tmp) {continue}
|
|
|
|
|
tmp.verticalCombo = true
|
|
|
|
|
|
|
|
|
|
newPoints++
|
|
|
|
|
}
|
|
|
|
|
// console.log(x, y)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (checkupCount) {
|
|
|
|
|
await this.asyncSetState({
|
|
|
|
|
items,
|
|
|
|
|
points: this.state.points + newPoints,
|
|
|
|
|
combo: this.state.combo+checkupCount,
|
|
|
|
|
comboMax: Math.max(this.state.comboMax, this.state.combo+checkupCount)
|
|
|
|
|
})
|
|
|
|
|
await new Promise((res) => setTimeout(res, 500))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return
|
|
|
|
|
|
|
|
|
|
// Clear items
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
|
|
|
for (let y = 0; y < items.length; y++) {
|
|
|
|
|
const row = items[y]
|
|
|
|
|
for (let x = 0; x < row.length; x++) {
|
|
|
|
|
const cell = row[x]
|
|
|
|
|
if (!cell || (!cell.horizontalCombo && !cell.verticalCombo)) {continue}
|
|
|
|
|
items[y][x] = undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let itemHasFallen = false
|
|
|
|
|
let needNewTurn = false
|
|
|
|
|
let needContinue = false
|
|
|
|
|
do {
|
|
|
|
|
// Make items fall
|
|
|
|
|
itemHasFallen = false
|
|
|
|
|
needContinue = false
|
|
|
|
|
for (let y = (items.length - 1); y >= 0; y--) {
|
|
|
|
|
const row = items[y]
|
|
|
|
|
for (let x = 0; x < row.length; x++) {
|
|
|
|
@@ -256,37 +258,107 @@ export default class PokemonShuffle extends React.Component<unknown, States> {
|
|
|
|
|
}
|
|
|
|
|
if (cell && y+1 < row.length && !items[y+1][x]) {
|
|
|
|
|
cell.isFalling = true
|
|
|
|
|
needNewTurn = true
|
|
|
|
|
itemHasFallen = true
|
|
|
|
|
needContinue = true
|
|
|
|
|
// Move cell down
|
|
|
|
|
items[y+1][x] = cell
|
|
|
|
|
items[y][x] = undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fill to top lane
|
|
|
|
|
// Fill the top lane
|
|
|
|
|
for (let x = 0; x < items[0].length; x++) {
|
|
|
|
|
const cell = items[0][x]
|
|
|
|
|
if (!cell) {
|
|
|
|
|
itemHasFallen = true
|
|
|
|
|
items[0][x] = {id: random(0, ITEM_COUNT), justSpawned: true}
|
|
|
|
|
needContinue = true
|
|
|
|
|
items[0][x] = {id: random(0, ITEM_COUNT), id2: n++, justSpawned: true}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (itemHasFallen) {
|
|
|
|
|
await this.asyncSetState({items})
|
|
|
|
|
await new Promise((res) => setTimeout(res, 300))
|
|
|
|
|
}
|
|
|
|
|
} while (itemHasFallen)
|
|
|
|
|
|
|
|
|
|
// If an item has fallen re calculate
|
|
|
|
|
if (needNewTurn) {
|
|
|
|
|
this.setState({items}, () => this.calculate())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
this.setState({items, loading: undefined, turn: this.state.turn+1, combo: 0})
|
|
|
|
|
// Need to wait for the falling animation
|
|
|
|
|
if (needContinue) {
|
|
|
|
|
await this.asyncSetState({items})
|
|
|
|
|
if (!initial) {
|
|
|
|
|
await wait(300)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Checkup if there is combos
|
|
|
|
|
const checkup = await this.checkup()
|
|
|
|
|
if (!checkup && !needContinue) {
|
|
|
|
|
return await this.endTurn({items})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clear items
|
|
|
|
|
let hasCleared = false
|
|
|
|
|
for (const row of items) {
|
|
|
|
|
for (let x = 0; x < row.length; x++) {
|
|
|
|
|
const cell = row[x]
|
|
|
|
|
if (!cell || (!cell.horizontalCombo && !cell.verticalCombo)) {continue}
|
|
|
|
|
row[x] = undefined
|
|
|
|
|
hasCleared = true
|
|
|
|
|
needContinue = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hasCleared && !initial) {
|
|
|
|
|
await wait(500)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} while (needContinue)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function calculateScore(len: number, combo: number) {
|
|
|
|
|
let score = len * 40 // currently the damage
|
|
|
|
|
if (len > 3) {
|
|
|
|
|
switch (len) {
|
|
|
|
|
case 4:
|
|
|
|
|
score *= 1.5
|
|
|
|
|
break
|
|
|
|
|
case 5:
|
|
|
|
|
score *= 2
|
|
|
|
|
break
|
|
|
|
|
case 6:
|
|
|
|
|
score *= 3
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (combo > 1) {
|
|
|
|
|
if (combo >= 2 && combo <= 4) {
|
|
|
|
|
score *= 1.1
|
|
|
|
|
}
|
|
|
|
|
if (combo >= 5 && combo <= 9) {
|
|
|
|
|
score *= 1.1
|
|
|
|
|
}
|
|
|
|
|
if (combo >= 10 && combo <= 24) {
|
|
|
|
|
score *= 1.2
|
|
|
|
|
}
|
|
|
|
|
if (combo >= 25 && combo <= 49) {
|
|
|
|
|
score *= 1.3
|
|
|
|
|
}
|
|
|
|
|
if (combo >= 50 && combo <= 74) {
|
|
|
|
|
score *= 1.4
|
|
|
|
|
}
|
|
|
|
|
if (combo >= 75 && combo <= 99) {
|
|
|
|
|
score *= 1.5
|
|
|
|
|
}
|
|
|
|
|
if (combo >= 100 && combo <= 199) {
|
|
|
|
|
score *= 2
|
|
|
|
|
}
|
|
|
|
|
if (combo >= 200) {
|
|
|
|
|
score *= 2.5
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return score
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function random(min = 0, max = 100) {
|
|
|
|
|
return Math.floor(Math.random() * (max - min) + min)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function wait(time: number): Promise<void> {
|
|
|
|
|
return new Promise((res) => setTimeout(() => res(), time))
|
|
|
|
|
}
|
|
|
|
|