Add another tsx demo
This commit is contained in:
parent
bec0d44af0
commit
b3a6321dcc
716
doric-demo/src/NewGobangDemo.tsx
Normal file
716
doric-demo/src/NewGobangDemo.tsx
Normal file
@ -0,0 +1,716 @@
|
||||
import {
|
||||
Stack,
|
||||
Group,
|
||||
Color,
|
||||
layoutConfig,
|
||||
LayoutSpec,
|
||||
vlayout,
|
||||
Text,
|
||||
ViewHolder,
|
||||
ViewModel,
|
||||
VMPanel,
|
||||
modal,
|
||||
text,
|
||||
Gravity,
|
||||
View,
|
||||
popover,
|
||||
Scroller,
|
||||
jsx,
|
||||
VLayout,
|
||||
makeRef,
|
||||
HLayout,
|
||||
} from "doric";
|
||||
import { colors } from "./utils";
|
||||
|
||||
enum State {
|
||||
Unspecified,
|
||||
BLACK,
|
||||
WHITE,
|
||||
}
|
||||
|
||||
const count = 13;
|
||||
|
||||
class AIComputer {
|
||||
wins: Array<Array<{ x: number; y: number }>> = [];
|
||||
winCount = 0;
|
||||
matrix: Map<number, State>;
|
||||
constructor(matrix: Map<number, State>) {
|
||||
this.matrix = matrix;
|
||||
for (let y = 0; y < count; y++) {
|
||||
for (let x = 0; x < count - 4; x++) {
|
||||
this.wins.push([]);
|
||||
for (let k = 0; k < 5; k++) {
|
||||
this.wins[this.winCount].push({
|
||||
x: x + k,
|
||||
y,
|
||||
});
|
||||
}
|
||||
this.winCount++;
|
||||
}
|
||||
}
|
||||
|
||||
for (let x = 0; x < count; x++) {
|
||||
for (let y = 0; y < count - 4; y++) {
|
||||
this.wins.push([]);
|
||||
for (let k = 0; k < 5; k++) {
|
||||
this.wins[this.winCount].push({
|
||||
x,
|
||||
y: y + k,
|
||||
});
|
||||
}
|
||||
this.winCount++;
|
||||
}
|
||||
}
|
||||
|
||||
for (let x = 0; x < count - 4; x++) {
|
||||
for (let y = 0; y < count - 4; y++) {
|
||||
this.wins.push([]);
|
||||
for (let k = 0; k < 5; k++) {
|
||||
this.wins[this.winCount].push({
|
||||
x: x + k,
|
||||
y: y + k,
|
||||
});
|
||||
}
|
||||
this.winCount++;
|
||||
}
|
||||
}
|
||||
|
||||
for (let x = 0; x < count - 4; x++) {
|
||||
for (let y = count - 1; y > 3; y--) {
|
||||
this.wins.push([]);
|
||||
for (let k = 0; k < 5; k++) {
|
||||
this.wins[this.winCount].push({
|
||||
x: x + k,
|
||||
y: y - k,
|
||||
});
|
||||
}
|
||||
this.winCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get blackWins() {
|
||||
return this.wins.map((win) => {
|
||||
let idx = 0;
|
||||
for (let e of win) {
|
||||
switch (this.matrix.get(e.x + e.y * count)) {
|
||||
case State.BLACK:
|
||||
idx++;
|
||||
break;
|
||||
case State.WHITE:
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
});
|
||||
}
|
||||
|
||||
get whiteWins() {
|
||||
return this.wins.map((win) => {
|
||||
let idx = 0;
|
||||
for (let e of win) {
|
||||
switch (this.matrix.get(e.x + e.y * count)) {
|
||||
case State.WHITE:
|
||||
idx++;
|
||||
break;
|
||||
case State.BLACK:
|
||||
return 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
});
|
||||
}
|
||||
|
||||
compute(matrix: State[], role: State.BLACK | State.WHITE) {
|
||||
const myScore = new Array(matrix.length).fill(0);
|
||||
const rivalScore = new Array(matrix.length).fill(0);
|
||||
const myWins = role === State.BLACK ? this.blackWins : this.whiteWins;
|
||||
const rivalWins = role === State.BLACK ? this.whiteWins : this.blackWins;
|
||||
let max = 0;
|
||||
let retIdx = 0;
|
||||
matrix.forEach((state, idx) => {
|
||||
if (state != State.Unspecified) {
|
||||
return;
|
||||
}
|
||||
this.wins.forEach((e, winIdx) => {
|
||||
if (e.filter((e) => e.x + e.y * count === idx).length === 0) {
|
||||
return;
|
||||
}
|
||||
switch (rivalWins[winIdx]) {
|
||||
case 1:
|
||||
rivalScore[idx] += 1;
|
||||
break;
|
||||
case 2:
|
||||
rivalScore[idx] += 10;
|
||||
break;
|
||||
case 3:
|
||||
rivalScore[idx] += 100;
|
||||
break;
|
||||
case 4:
|
||||
rivalScore[idx] += 10000;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (myWins[winIdx]) {
|
||||
case 1:
|
||||
myScore[idx] += 2;
|
||||
break;
|
||||
case 2:
|
||||
myScore[idx] += 20;
|
||||
break;
|
||||
case 3:
|
||||
myScore[idx] += 200;
|
||||
break;
|
||||
case 4:
|
||||
myScore[idx] += 20000;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (rivalScore[idx] > max) {
|
||||
max = rivalScore[idx];
|
||||
retIdx = idx;
|
||||
} else if (rivalScore[idx] == max) {
|
||||
if (myScore[idx] > myScore[retIdx]) {
|
||||
retIdx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
if (myScore[idx] > max) {
|
||||
max = myScore[idx];
|
||||
retIdx = idx;
|
||||
} else if (myScore[idx] == max) {
|
||||
if (rivalScore[idx] > rivalScore[retIdx]) {
|
||||
retIdx = idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
return retIdx;
|
||||
}
|
||||
}
|
||||
const lineColor = Color.BLACK;
|
||||
|
||||
enum GameMode {
|
||||
P2P,
|
||||
P2C,
|
||||
C2P,
|
||||
}
|
||||
|
||||
interface GoBangState {
|
||||
count: number;
|
||||
gap: number;
|
||||
role: "white" | "black";
|
||||
matrix: Map<number, State>;
|
||||
anchor?: number;
|
||||
gameMode: GameMode;
|
||||
gameState: "blackWin" | "whiteWin" | "idle";
|
||||
}
|
||||
|
||||
class GoBangVH extends ViewHolder {
|
||||
root!: Group;
|
||||
gap = 0;
|
||||
currentRole = makeRef<Text>();
|
||||
result = makeRef<Text>();
|
||||
targetZone: View[] = [];
|
||||
gameMode = makeRef<Text>();
|
||||
assistant = makeRef<Text>();
|
||||
build(root: Group): void {
|
||||
this.root = root;
|
||||
}
|
||||
actualBuild(state: GoBangState): void {
|
||||
const boardSize = state.gap * (state.count - 1);
|
||||
const gap = state.gap;
|
||||
const borderWidth = gap;
|
||||
this.gap = state.gap;
|
||||
|
||||
<Scroller parent={this.root}>
|
||||
<VLayout backgroundColor={Color.parse("#ecf0f1")}>
|
||||
<Text
|
||||
text="五子棋"
|
||||
layoutConfig={layoutConfig().configWidth(LayoutSpec.MOST)}
|
||||
textSize={30}
|
||||
textColor={Color.WHITE}
|
||||
backgroundColor={colors[0]}
|
||||
textAlignment={Gravity.Center}
|
||||
height={50}
|
||||
/>
|
||||
<Stack
|
||||
layoutConfig={layoutConfig().just()}
|
||||
width={boardSize + 2 * borderWidth}
|
||||
height={boardSize + 2 * borderWidth}
|
||||
backgroundColor={Color.parse("#E6B080")}
|
||||
>
|
||||
<Stack
|
||||
layoutConfig={layoutConfig()
|
||||
.just()
|
||||
.configMargin({ top: borderWidth, left: borderWidth })}
|
||||
width={boardSize}
|
||||
height={boardSize}
|
||||
border={{
|
||||
width: 1,
|
||||
color: lineColor,
|
||||
}}
|
||||
>
|
||||
{new Array(count - 2).fill(0).map((_, idx) => (
|
||||
<Stack
|
||||
layoutConfig={layoutConfig().justWidth().mostHeight()}
|
||||
width={1}
|
||||
backgroundColor={lineColor}
|
||||
left={(idx + 1) * gap}
|
||||
/>
|
||||
))}
|
||||
|
||||
{new Array(count - 2).fill(0).map((_, idx) => (
|
||||
<Stack
|
||||
layoutConfig={layoutConfig().mostWidth().justHeight()}
|
||||
height={1}
|
||||
backgroundColor={lineColor}
|
||||
top={(idx + 1) * gap}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
{
|
||||
(this.targetZone = new Array(count * count)
|
||||
.fill(0)
|
||||
.map((_, idx) => (
|
||||
<Stack
|
||||
layoutConfig={layoutConfig().just()}
|
||||
width={gap}
|
||||
height={gap}
|
||||
top={(Math.floor(idx / count) - 0.5) * gap + borderWidth}
|
||||
left={((idx % count) - 0.5) * gap + borderWidth}
|
||||
/>
|
||||
)))
|
||||
}
|
||||
</Stack>
|
||||
<Text
|
||||
ref={this.gameMode}
|
||||
textSize={20}
|
||||
textColor={Color.WHITE}
|
||||
layoutConfig={layoutConfig().mostWidth().justHeight()}
|
||||
height={50}
|
||||
backgroundColor={colors[8]}
|
||||
>
|
||||
游戏模式
|
||||
</Text>
|
||||
<HLayout layoutConfig={layoutConfig().mostWidth().fitHeight()}>
|
||||
<Text
|
||||
ref={this.currentRole}
|
||||
textSize={20}
|
||||
textColor={Color.WHITE}
|
||||
layoutConfig={layoutConfig().just().configWeight(1)}
|
||||
height={50}
|
||||
backgroundColor={colors[1]}
|
||||
>
|
||||
当前:
|
||||
</Text>
|
||||
<Text
|
||||
ref={this.result}
|
||||
textSize={20}
|
||||
textColor={Color.WHITE}
|
||||
layoutConfig={layoutConfig().just().configWeight(1)}
|
||||
height={50}
|
||||
backgroundColor={colors[2]}
|
||||
>
|
||||
获胜方:
|
||||
</Text>
|
||||
</HLayout>
|
||||
<Text
|
||||
ref={this.assistant}
|
||||
textSize={20}
|
||||
textColor={Color.WHITE}
|
||||
layoutConfig={layoutConfig().justHeight().mostWidth()}
|
||||
height={50}
|
||||
backgroundColor={colors[3]}
|
||||
>
|
||||
提示
|
||||
</Text>
|
||||
</VLayout>
|
||||
</Scroller>;
|
||||
}
|
||||
}
|
||||
|
||||
class GoBangVM extends ViewModel<GoBangState, GoBangVH> {
|
||||
computer!: AIComputer;
|
||||
onAttached(state: GoBangState, vh: GoBangVH) {
|
||||
if (!this.computer) {
|
||||
this.computer = new AIComputer(state.matrix);
|
||||
}
|
||||
vh.actualBuild(state);
|
||||
vh.targetZone.forEach((e, idx) => {
|
||||
e.onClick = () => {
|
||||
if (state.gameState !== "idle") {
|
||||
return;
|
||||
}
|
||||
const zoneState = state.matrix.get(idx);
|
||||
if (zoneState === State.BLACK || zoneState === State.WHITE) {
|
||||
modal(context).toast("This position had been token.");
|
||||
return;
|
||||
}
|
||||
if (state.anchor === undefined || state.anchor != idx) {
|
||||
this.updateState((it) => {
|
||||
it.anchor = idx;
|
||||
});
|
||||
} else {
|
||||
this.updateState((it) => {
|
||||
if (it.role === "black") {
|
||||
it.matrix.set(idx, State.BLACK);
|
||||
it.role = "white";
|
||||
} else {
|
||||
it.matrix.set(idx, State.WHITE);
|
||||
it.role = "black";
|
||||
}
|
||||
it.anchor = undefined;
|
||||
if (this.checkResult(idx)) {
|
||||
modal(context).toast(
|
||||
`恭喜获胜方${it.role === "white" ? "黑方" : "白方"}`
|
||||
);
|
||||
it.gameState = it.role === "white" ? "blackWin" : "whiteWin";
|
||||
} else {
|
||||
if (it.role === "black" && it.gameMode === GameMode.C2P) {
|
||||
setTimeout(() => {
|
||||
this.computeNextStep(it);
|
||||
}, 0);
|
||||
} else if (it.role === "white" && it.gameMode === GameMode.P2C) {
|
||||
setTimeout(() => {
|
||||
this.computeNextStep(it);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
vh.gameMode.current.onClick = () => {
|
||||
popover(context).show(
|
||||
vlayout(
|
||||
[
|
||||
...[
|
||||
{
|
||||
label: "黑方:人 白方:人",
|
||||
mode: GameMode.P2P,
|
||||
},
|
||||
{
|
||||
label: "黑方:人 白方:机",
|
||||
mode: GameMode.P2C,
|
||||
},
|
||||
{
|
||||
label: "黑方:机 白方:人",
|
||||
mode: GameMode.C2P,
|
||||
},
|
||||
].map((e) =>
|
||||
text({
|
||||
text: e.label,
|
||||
textSize: 20,
|
||||
textColor: Color.WHITE,
|
||||
layoutConfig: layoutConfig().just(),
|
||||
height: 50,
|
||||
width: 300,
|
||||
backgroundColor:
|
||||
state.gameMode === e.mode
|
||||
? Color.parse("#636e72")
|
||||
: Color.parse("#b2bec3"),
|
||||
onClick: () => {
|
||||
this.updateState((s) => {
|
||||
s.gameMode = e.mode;
|
||||
this.reset(s);
|
||||
});
|
||||
popover(context).dismiss();
|
||||
},
|
||||
})
|
||||
),
|
||||
],
|
||||
{
|
||||
layoutConfig: layoutConfig().most(),
|
||||
onClick: () => {
|
||||
popover(context).dismiss();
|
||||
},
|
||||
gravity: Gravity.Center,
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
vh.result.current.onClick = () => {
|
||||
switch (state.gameState) {
|
||||
case "idle":
|
||||
this.updateState((state) => {
|
||||
this.reset(state);
|
||||
});
|
||||
break;
|
||||
case "blackWin":
|
||||
case "whiteWin":
|
||||
break;
|
||||
}
|
||||
};
|
||||
vh.currentRole.current.onClick = () => {
|
||||
switch (state.gameState) {
|
||||
case "idle":
|
||||
break;
|
||||
case "blackWin":
|
||||
case "whiteWin":
|
||||
this.updateState((state) => {
|
||||
this.reset(state);
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
vh.assistant.current.onClick = () => {
|
||||
const it = this.getState();
|
||||
if (it.gameState !== "idle") {
|
||||
return;
|
||||
}
|
||||
this.computeNextStep(it);
|
||||
if (it.gameState !== "idle") {
|
||||
return;
|
||||
}
|
||||
if (it.role === "black" && it.gameMode === GameMode.C2P) {
|
||||
setTimeout(() => {
|
||||
this.computeNextStep(it);
|
||||
}, 0);
|
||||
} else if (it.role === "white" && it.gameMode === GameMode.P2C) {
|
||||
setTimeout(() => {
|
||||
this.computeNextStep(it);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
computeNextStep(it: GoBangState) {
|
||||
const tempMatrix: State[] = new Array(count * count)
|
||||
.fill(0)
|
||||
.map((_, idx) => {
|
||||
return it.matrix.get(idx) || State.Unspecified;
|
||||
});
|
||||
let idx = 0;
|
||||
do {
|
||||
idx = this.computer.compute(
|
||||
tempMatrix,
|
||||
it.role === "black" ? State.BLACK : State.WHITE
|
||||
);
|
||||
} while (it.matrix.get(idx) === State.Unspecified);
|
||||
this.updateState((state) => {
|
||||
state.matrix.set(idx, state.role === "black" ? State.BLACK : State.WHITE);
|
||||
state.role = state.role === "black" ? "white" : "black";
|
||||
if (this.checkResult(idx)) {
|
||||
modal(context).toast(
|
||||
`恭喜获胜方${it.role === "white" ? "黑方" : "白方"}`
|
||||
);
|
||||
it.gameState = it.role === "white" ? "blackWin" : "whiteWin";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reset(it: GoBangState) {
|
||||
it.matrix.clear();
|
||||
it.gameState = "idle";
|
||||
it.role = "black";
|
||||
it.anchor = undefined;
|
||||
this.computer = new AIComputer(it.matrix);
|
||||
if (it.gameMode === GameMode.C2P) {
|
||||
const idx =
|
||||
Math.floor(Math.random() * count) * count +
|
||||
Math.floor(Math.random() * count);
|
||||
it.matrix.set(idx, State.BLACK);
|
||||
it.role = "white";
|
||||
}
|
||||
}
|
||||
onBind(state: GoBangState, vh: GoBangVH) {
|
||||
vh.targetZone.forEach((v, idx) => {
|
||||
const zoneState = state.matrix.get(idx);
|
||||
switch (zoneState) {
|
||||
case State.BLACK:
|
||||
v.also((it) => {
|
||||
it.backgroundColor = Color.BLACK;
|
||||
it.corners = state.gap / 2;
|
||||
it.border = {
|
||||
color: Color.TRANSPARENT,
|
||||
width: 0,
|
||||
};
|
||||
});
|
||||
break;
|
||||
case State.WHITE:
|
||||
v.also((it) => {
|
||||
it.backgroundColor = Color.WHITE;
|
||||
it.corners = state.gap / 2;
|
||||
it.border = {
|
||||
color: Color.TRANSPARENT,
|
||||
width: 0,
|
||||
};
|
||||
});
|
||||
break;
|
||||
default:
|
||||
v.also((it) => {
|
||||
it.backgroundColor = Color.TRANSPARENT;
|
||||
it.corners = 0;
|
||||
it.border = {
|
||||
color: Color.TRANSPARENT,
|
||||
width: 0,
|
||||
};
|
||||
});
|
||||
break;
|
||||
}
|
||||
if (state.anchor === idx) {
|
||||
v.also((it) => {
|
||||
it.backgroundColor = Color.RED.alpha(0.1);
|
||||
it.corners = 0;
|
||||
it.border = {
|
||||
color: Color.RED,
|
||||
width: 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
vh.gameMode.current.text = `游戏模式: 黑方 ${
|
||||
state.gameMode === GameMode.C2P ? "机" : "人"
|
||||
} 白方 ${state.gameMode === GameMode.P2C ? "机" : "人"}`;
|
||||
switch (state.gameState) {
|
||||
case "idle":
|
||||
vh.result.current.text = "重新开始";
|
||||
vh.currentRole.current.text = `当前: ${
|
||||
state.role === "black" ? "黑方" : "白方"
|
||||
}`;
|
||||
break;
|
||||
case "blackWin":
|
||||
vh.result.current.text = "黑方获胜";
|
||||
vh.currentRole.current.text = "重新开始";
|
||||
break;
|
||||
case "whiteWin":
|
||||
vh.result.current.text = "白方获胜";
|
||||
vh.currentRole.current.text = "重新开始";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
checkResult(pos: number) {
|
||||
const matrix = this.getState().matrix;
|
||||
const state = matrix.get(pos);
|
||||
const y = Math.floor(pos / count);
|
||||
const x = pos % count;
|
||||
const getState = (x: number, y: number) => matrix.get(y * count + x);
|
||||
///Horitonzal
|
||||
{
|
||||
let left = x;
|
||||
while (left >= 1) {
|
||||
if (getState(left - 1, y) === state) {
|
||||
left -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let right = x;
|
||||
while (right <= count - 2) {
|
||||
if (getState(right + 1, y) === state) {
|
||||
right += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (right - left >= 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
///Vertical
|
||||
{
|
||||
let top = y;
|
||||
while (top >= 1) {
|
||||
if (getState(x, top - 1) === state) {
|
||||
top -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let bottom = y;
|
||||
while (bottom <= count - 2) {
|
||||
if (getState(x, bottom + 1) === state) {
|
||||
bottom += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bottom - top >= 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
///LT-RB
|
||||
{
|
||||
let startX = x,
|
||||
startY = y;
|
||||
while (startX >= 1 && startY >= 1) {
|
||||
if (getState(startX - 1, startY - 1) === state) {
|
||||
startX -= 1;
|
||||
startY -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let endX = x,
|
||||
endY = y;
|
||||
while (endX <= count - 2 && endY <= count - 2) {
|
||||
if (getState(endX + 1, endY + 1) === state) {
|
||||
endX += 1;
|
||||
endY += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (endX - startX >= 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
///LB-RT
|
||||
{
|
||||
let startX = x,
|
||||
startY = y;
|
||||
while (startX >= 1 && startY <= count + 2) {
|
||||
if (getState(startX - 1, startY + 1) === state) {
|
||||
startX -= 1;
|
||||
startY += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let endX = x,
|
||||
endY = y;
|
||||
while (endX <= count - 2 && endY >= 1) {
|
||||
if (getState(endX + 1, endY - 1) === state) {
|
||||
endX += 1;
|
||||
endY -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (endX - startX >= 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Entry
|
||||
class Gobang extends VMPanel<GoBangState, GoBangVH> {
|
||||
getViewModelClass() {
|
||||
return GoBangVM;
|
||||
}
|
||||
getState(): GoBangState {
|
||||
return {
|
||||
count,
|
||||
gap: this.getRootView().width / 14,
|
||||
role: "black",
|
||||
matrix: new Map(),
|
||||
gameMode: GameMode.P2C,
|
||||
gameState: "idle",
|
||||
};
|
||||
}
|
||||
getViewHolderClass() {
|
||||
return GoBangVH;
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import { Group, View } from "../ui/view";
|
||||
import { layoutConfig } from "./layoutconfig";
|
||||
import { loge } from "./log";
|
||||
import { ClassType } from "./types";
|
||||
|
||||
export namespace jsx {
|
||||
|
Reference in New Issue
Block a user