Skip to content

Commit

Permalink
Merge branch 'develop' into deploy-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
thangved committed Dec 4, 2023
2 parents 967c3d0 + 95be14a commit cbdae8e
Show file tree
Hide file tree
Showing 8 changed files with 464 additions and 520 deletions.
751 changes: 290 additions & 461 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 36 additions & 4 deletions src/components/game-board/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ArrowUpOutlined,
DoubleRightOutlined,
FormatPainterFilled,
PlusOutlined,
StopOutlined,
UserOutlined,
} from '@ant-design/icons';
Expand Down Expand Up @@ -131,7 +132,8 @@ export default function GameBoard({ state, action }: GameBoardProps) {
const hashed: {
[id: string]: Action | null;
} = {};
for (const act of action.actions) {

for (const act of action.actions ?? []) {
hashed[act.craftsman_id] = act;
}

Expand Down Expand Up @@ -228,19 +230,20 @@ export default function GameBoard({ state, action }: GameBoardProps) {

{state.buildPositions.map((pos) => (
<div
key={`${pos.x}-${pos.y}`}
key={`build:${pos.x}-${pos.y}`}
className={clsx(styles.position)}
style={{
transform: `translate(${pos.x * 33}px, ${pos.y * 33}px)`,
fontSize: 10,
}}
>
{pos.data}
<PlusOutlined />
</div>
))}

{state.destroyPositions.map((pos) => (
<div
key={`${pos.x}-${pos.y}`}
key={`destroy:${pos.x}-${pos.y}`}
className={clsx(styles.position)}
style={{
transform: `translate(${pos.x * 33}px, ${pos.y * 33 - 3}px)`,
Expand All @@ -250,6 +253,35 @@ export default function GameBoard({ state, action }: GameBoardProps) {
<StopOutlined />
</div>
))}

{state.targetPositions.map((pos, i) => (
<div
key={`target:${i}`}
className={clsx(styles.position)}
style={{
transform: `translate(${pos.x * 33}px, ${pos.y * 33}px)`,
zIndex: state.height,
border: '2px solid red',
}}
></div>
))}

{state.scorePositions.map((pos, i) => (
<div
key={`score:${i}`}
className={clsx(styles.position)}
style={{
transform: `translate(${pos.x * 33}px, ${pos.y * 33}px)`,
zIndex: state.height,
fontSize: 10,
fontWeight: 'normal',
// color: 'blue',
}}
>
{pos.data.toFixed(1)}
</div>
))}

<div
className={styles.mask}
style={{ zIndex: state.height }}
Expand Down
1 change: 1 addition & 0 deletions src/components/play-real-tab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export default function PlayRealTab() {
message.error(error.message);
} finally {
setIsPlaying(false);
setIsShowCountDown(false);
dispatch(setCurrentAction(undefined));
}
}, [dispatch, game, side, gameMode]);
Expand Down
134 changes: 97 additions & 37 deletions src/game/classes/CaroGameManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ const BUILD_TEMPLATE = [
[false, false, true, false, false],
];

// const BUILD_TEMPLATE = [
// [false, true, false],
// [true, false, true],
// [false, true, false],
// ];

const TEMPLATE_WIDTH = BUILD_TEMPLATE[0].length;
const TEMPLATE_HEIGHT = BUILD_TEMPLATE.length;

Expand All @@ -39,6 +45,7 @@ export default class CaroGameManager extends GameManager implements ICaroGameMan
private scoreCounter: HashedCounter = new HashedCounter();

private prepareCreatePositions(side: EWallSide = 'A') {
this.targetPositions = [];
this.hashedBuildPositions = new HashedType<boolean>();
this.hashedDestroyPositions = new HashedType<boolean>();
this.scoreCounter = new HashedCounter();
Expand All @@ -49,8 +56,6 @@ export default class CaroGameManager extends GameManager implements ICaroGameMan
{
// Nếu có thể xây, thì xây
if (this.checkCreate(pos, side)) this.hashedBuildPositions.write(pos, true);
// Nếu không thể xây, thì tăng điểm
else this.scoreCounter.increase(new Position(pos.x / TEMPLATE_WIDTH, pos.y / TEMPLATE_HEIGHT), 2);
}
{
// Check destroy scoped
Expand All @@ -64,17 +69,66 @@ export default class CaroGameManager extends GameManager implements ICaroGameMan
if (this.hashedWalls.exist(pos)) continue;
if (this.hashedCastles.exist(pos)) continue;
if (this.hashedPonds.exist(pos)) continue;
if (this.hashedCraftmens.exist(pos)) continue;

this.hashedBuildPositions.write(pos, true);
}
}

for (const pond of this.ponds) {
for (const pos of pond.topRightBottomLeft()) {
if (this.hashedWalls.exist(pos)) continue;
if (this.hashedCastles.exist(pos)) continue;
if (this.hashedPonds.exist(pos)) continue;
if (this.hashedCraftmens.exist(pos)) continue;

this.hashedBuildPositions.write(pos, true);
}
}

this.buildPositions = this.hashedBuildPositions.toList().map((pos) => ({
...pos,
data: this.scoreCounter.read(new Position(pos.x / TEMPLATE_WIDTH, pos.y / TEMPLATE_HEIGHT)),
}));
for (let x = 0; x < this.width; x++) {
for (let y = 0; y < this.height; y++) {
const pos = new Position(x, y);

if (this.hashedBuildPositions.exist(pos)) continue;
if (this.hashedWalls.exist(pos)) continue;
if (this.hashedPonds.exist(pos)) continue;
if (this.hashedSide.exist(pos) && this.hashedSide.read(pos) === side) continue;

const trbl = pos.topRightBottomLeft();
let builds = 0;

for (const p of trbl) {
if (this.hashedBuildPositions.exist(p)) builds++;
if (this.hashedWalls.exist(p) && this.hashedWalls.read(p)!.side !== side)
this.scoreCounter.increase(pos, 0.25);
if (this.hashedCraftmens.exist(pos) && this.hashedCraftmens.read(pos)!.side !== side)
this.scoreCounter.decrease(pos, 0.25);
}

switch (builds) {
case 1:
this.scoreCounter.write(new Position(x, y), 1.5);
break;
case 2:
this.scoreCounter.write(new Position(x, y), 1.25);
break;
case 3:
this.scoreCounter.write(new Position(x, y), 1);
break;
case 0:
break;
default:
this.scoreCounter.write(new Position(x, y), 0.5);
}

if (this.hashedCastles.exist(pos)) this.scoreCounter.increase(pos, 1);
}
}

this.buildPositions = this.hashedBuildPositions.toList();
this.destroyPositions = this.hashedDestroyPositions.toList();
this.scorePositions = this.scoreCounter.toList();
}

private checkCreate(pos: Position, ownSide: EWallSide): boolean {
Expand Down Expand Up @@ -142,7 +196,7 @@ export default class CaroGameManager extends GameManager implements ICaroGameMan
const nextAction = this.getActionToGoToPosition(craftmen, pos);
if (!nextAction) return craftmen.getStayAction();

this.goingTo.write(pos, pos);
this.targetPositions.push(pos);

// If the craftsman can not do anything, stay
return nextAction;
Expand All @@ -154,27 +208,23 @@ export default class CaroGameManager extends GameManager implements ICaroGameMan
* @returns Next position for the craftsman
*/
private getNextPosition(craftmen: CraftsmenPosition): Position | null {
const closestCastle = this.findClosestCastle(craftmen);
if (closestCastle) {
for (const pos of closestCastle.topRightBottomLeft()) {
this.hashedBuildPositions.remove(pos);
}
// const closestCastle = this.findClosestCastle(craftmen);
// if (closestCastle) {
// for (const pos of closestCastle.topRightBottomLeft()) {
// this.hashedBuildPositions.remove(pos);
// }

return closestCastle;
}
// return closestCastle;
// }

// Initialize positions array, use this like a queue
const queue = new PriorityQueue<Position>();
const visited = new HashedType<boolean>();

const allNears = craftmen.allNears();
queue.enQueue(craftmen, 0);

for (const near of allNears) {
queue.enQueue(
near,
-this.scoreCounter.read(new Position(near.x / TEMPLATE_WIDTH, near.y / TEMPLATE_HEIGHT)),
);
}
let bestScore = Infinity;
let bestPos: Position | null = null;

while (!queue.isEmpty()) {
// Get the first position in the positions array and remove it from the array
Expand All @@ -186,41 +236,51 @@ export default class CaroGameManager extends GameManager implements ICaroGameMan

// If the position is not valid, continue
if (!pos.isValid(this.width, this.height)) continue;
// if (pos.distance(craftmen) > 15 && bestPos) break;
if (this.hashedPonds.exist(pos)) continue;
if (this.hashedWalls.exist(pos) && this.hashedWalls.read(pos)!.side !== craftmen.side) continue;
// if (this.hashedWalls.exist(pos) && this.hashedWalls.read(pos)!.side !== craftmen.side) continue;

const trbl = pos.topRightBottomLeft();

for (const near of trbl) {
if (this.hashedBuildPositions.exist(near)) {
this.hashedBuildPositions.remove(near);
// Nếu xung quanh có con nào là con của mình thì bỏ qua
const haveOwnCraftmen = near
.topRightBottomLeft()
.some((p) => this.hashedCraftmens.exist(p) && this.hashedCraftmens.read(p)!.side === craftmen.side);
if (haveOwnCraftmen) continue;

pos.topRightBottomLeft().forEach((pos) => this.hashedBuildPositions.remove(pos));

return pos;
if (this.hashedBuildPositions.exist(near)) {
if (top.priority < bestScore) {
bestScore = top.priority;
bestPos = pos;
}
}

// if (this.hashedDestroyPositions.exist(near)) {
// this.hashedDestroyPositions.remove(near);
// return pos;
// }
if (this.hashedDestroyPositions.exist(near)) {
if (top.priority < bestScore) {
bestScore = top.priority;
bestPos = pos;
}
}
}

const allNears = pos.allNears();

for (const near of allNears) {
queue.enQueue(
near,
craftmen.distance(near) -
this.scoreCounter.read(new Position(near.x / TEMPLATE_WIDTH, near.y / TEMPLATE_HEIGHT)),
);
queue.enQueue(near, craftmen.distance(near) - this.scoreCounter.read(near));
}
}

console.log('Cannot find next position for the craftsman', craftmen);

// Khúc này xóa mark để mấy con khác không đi vào
for (const pos of bestPos?.topRightBottomLeft() ?? []) {
this.hashedBuildPositions.remove(pos);
this.hashedDestroyPositions.remove(pos);
}

// If the craftsman can not go to any position, return null
return null;
return bestPos;
}

/**
Expand Down
34 changes: 22 additions & 12 deletions src/game/classes/GameManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export default class GameManager extends BaseGameManager implements IGameManager
width: object.width,
buildPositions: object.buildPositions,
destroyPositions: object.destroyPositions,
targetPositions: object.targetPositions,
scorePositions: object.scorePositions,
};
}

Expand Down Expand Up @@ -171,10 +173,8 @@ export default class GameManager extends BaseGameManager implements IGameManager
// Get nearby positions of new wall and update side
const positions = pos.topRightBottomLeft();

const filled = new HashedType<boolean>();

for (const position of positions) {
this.updateSideFromPosition(position, this.hashedSide.read(position) || side, filled);
this.updateSideFromPosition(position, this.hashedSide.read(position) || side);
}

this.hashedSide.remove(pos);
Expand Down Expand Up @@ -252,33 +252,47 @@ export default class GameManager extends BaseGameManager implements IGameManager
protected getActionToGoToPosition(craftmen: CraftsmenPosition, targetPos: Position): ActionDto | null {
const queue = new PriorityQueue<{ from: Position; to: Position; direction: EMoveParam }>();
const visited = new HashedType<boolean>();
const dist = new HashedType<number>();

const nears = craftmen.allNears();

for (const i in nears) {
queue.enQueue({ from: nears[i], to: nears[i], direction: moveParams[i] }, 1);
dist.write(nears[i], 1);
}

let minDist = Infinity;
let finalAction: ActionDto | null = null;

while (!queue.isEmpty()) {
const top = queue.deQueue();
const pos = top.value.to;
const d = top.priority;

if (d >= minDist) continue;

if (!this.isValidPosition(pos)) continue;
if (this.hashedPonds.exist(pos)) continue;
if (this.hashedCraftmens.exist(pos)) continue;
if (this.hashedWalls.exist(pos) && this.hashedWalls.read(pos)!.side !== craftmen.side) continue;

if (pos.isEquals(targetPos)) return craftmen.getMoveAction(top.value.direction);
if (pos.isEquals(targetPos)) {
if (top.priority + 1 < minDist) {
minDist = d + 1;
finalAction = craftmen.getMoveAction(top.value.direction);
}
}

if (visited.exist(pos)) continue;
visited.write(pos, true);

for (const near of pos.allNears()) {
queue.enQueue({ from: top.value.from, to: near, direction: top.value.direction }, top.priority + 1);
queue.enQueue({ from: top.value.from, to: near, direction: top.value.direction }, d + 1);
if (!dist.exist(near) || dist.read(near)! > d + 1) dist.write(near, d + 1);
}
}

return null;
return finalAction;
}

/**
Expand Down Expand Up @@ -393,16 +407,12 @@ export default class GameManager extends BaseGameManager implements IGameManager
* @param visited - Visited positions
* @param filled - Filled positions
*/
private updateSideFromPosition(
pos: Position,
initSide: EWallSide | null = null,
filled: HashedType<boolean> = new HashedType<boolean>(),
): void {
private updateSideFromPosition(pos: Position, initSide: EWallSide | null = null): void {
// Get side of position
const updatedSide = this.sideOf(pos, initSide, new HashedType<boolean>());

// Fill side of position and nearby positions
this.fillSide(pos, updatedSide, filled);
this.fillSide(pos, updatedSide, new HashedType<boolean>());

// Update sides from hashed side
this.sides = this.hashedSide.toList();
Expand Down
Loading

0 comments on commit cbdae8e

Please sign in to comment.