Initial implementation of guide.

This commit is contained in:
yukirij 2024-12-17 17:18:34 -08:00
parent f34f695098
commit 842a945a01
8 changed files with 462 additions and 106 deletions

View File

@ -166,7 +166,7 @@ async fn main()
/*
** Initialize central bus and data serivce.
*/
let (data_tx, data_rx) = mpsc::channel::<QRPacket>(64);
let (data_tx, data_rx) = mpsc::channel::<QRPacket>(1024);
tokio::spawn(async move {
manager::thread_system(app, data_rx).await;
});
@ -221,6 +221,8 @@ async fn main()
WebCache::file("www/js/game_asset.js"),
WebCache::string(&js_asset_data),
WebCache::file("www/js/game.js"),
WebCache::file("www/js/game_config.js"),
WebCache::file("www/js/game_config_const.js"),
WebCache::file("www/js/interface.js"),
WebCache::file("www/js/ui.js"),
WebCache::file("www/js/scene.js"),

View File

@ -173,6 +173,11 @@ main>nav>section:first-child>button:hover {
color:#e0e0e0;
}
main>nav>section>button>canvas {
width: 2.5rem;
height: 100%;
}
main>nav>section>div{
display:block;
position:relative;

View File

@ -2,69 +2,32 @@ const GAME = { };
let GAME_DATA = null;
GAME.Board = class {
constructor() {
constructor(config=null) {
this.tiles = [ ]; for(let i = 0; i < 61; ++i) { this.tiles.push(new GAME.Tile(i)); }
this.pieces = [ ]; for(let i = 0; i < GAME.Const.Count.Pieces; ++i) { this.pieces.push(null); }
this.columns = [ ]; for(let i = 0; i < 9; ++i) { this.columns.push(new GAME.Column()); }
this.pieces = [ ];
this.init();
}
console.log(config);
init() {
this.pieces = [ ]; for(let i = 0; i < GAME.Const.Count.Pieces; ++i) { this.pieces.push(null); }
if(config !== null) {
for(let i = 0; i < config.count_pieces(); ++i) { this.pieces.push(null); }
// Describe Dawn layout
let layout = [
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(0, 1) },
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(1, 1) },
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(2, 2) },
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(3, 2) },
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(4, 3) },
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(5, 3) },
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(6, 4) },
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(7, 4) },
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(8, 5) },
{ piece:GAME.Const.PieceId.Lance, hex:new MATH.Vec2(0, 0) },
{ piece:GAME.Const.PieceId.Lance, hex:new MATH.Vec2(8, 4) },
{ piece:GAME.Const.PieceId.Knight, hex:new MATH.Vec2(1, 0) },
{ piece:GAME.Const.PieceId.Knight, hex:new MATH.Vec2(7, 3) },
{ piece:GAME.Const.PieceId.Castle, hex:new MATH.Vec2(2, 0) },
{ piece:GAME.Const.PieceId.Castle, hex:new MATH.Vec2(6, 2) },
{ piece:GAME.Const.PieceId.Tower, hex:new MATH.Vec2(3, 0) },
{ piece:GAME.Const.PieceId.Tower, hex:new MATH.Vec2(5, 1) },
{ piece:GAME.Const.PieceId.Dragon, hex:new MATH.Vec2(4, 2) },
{ piece:GAME.Const.PieceId.Behemoth, hex:new MATH.Vec2(4, 1) },
{ piece:GAME.Const.PieceId.Heart, hex:new MATH.Vec2(4, 0) },
];
// Add Dawn pieces
for(let lay of layout) {
this.set_piece(
lay.piece,
GAME.Const.Player.Dawn,
HEX.hex_to_tile(lay.hex)
);
}
// Add Dusk pieces
for(let lay of layout) {
this.set_piece(
lay.piece,
GAME.Const.Player.Dusk,
HEX.hex_to_tile(new MATH.Vec2(8 - lay.hex.x, 8 - lay.hex.y))
);
for(let lay of config.layout.pieces) {
this.set_piece(
lay.piece,
lay.player,
lay.promote,
HEX.hex_to_tile(lay.hex)
);
}
}
}
clone() {
let board = new GAME.Board();
for(let i = 0; i < this.tiles.length; ++i) { board.tiles[i] = this.tiles[i].clone(); }
for(let i = 0; i < this.pieces.length; ++i) { this.pieces.push(null); }
for(let i = 0; i < this.pieces.length; ++i) {
if(this.pieces[i] !== null) {
board.pieces[i] = this.pieces[i].clone();
@ -72,14 +35,16 @@ GAME.Board = class {
board.pieces[i] = null;
}
}
return board;
}
set_piece(piece, player, tile) {
set_piece(piece, player, promote, tile) {
let index = 0;
while(this.pieces[index] !== null) { index++; }
while(index < this.pieces.length && this.pieces[index] !== null) { index++; }
if(index == this.pieces.length) { this.pieces.push(null); }
let game_piece = new GAME.Piece(piece, player);
let game_piece = new GAME.Piece(piece, player, promote);
game_piece.tile = tile;
this.tiles[tile].piece = index;
this.pieces[index] = game_piece;
@ -199,10 +164,10 @@ GAME.PieceMovement = class {
};
GAME.Piece = class {
constructor(piece, player) {
constructor(piece, player, promote=false) {
this.piece = piece;
this.player = player;
this.promoted = false;
this.promoted = promote;
this.tile = 0;
this.blocking = 0;
@ -234,15 +199,24 @@ GAME.Piece = class {
};
GAME.Game = class {
constructor() {
constructor(config) {
this.config = config;
this.turn = 0;
this.board = new GAME.Board();
this.board = new GAME.Board(this.config);
this.pools = [
new GAME.Pool(),
new GAME.Pool(),
];
for(let i = 0; i < 7; ++i) {
this.pools[0].pieces[i] = this.config.pool[i];
}
for(let i = 0; i < 7; ++i) {
this.pools[1].pieces[i] = this.config.pool[i + 7];
}
this.state = {
code:0,
check:false,
@ -408,6 +382,7 @@ GAME.Game = class {
this.board.set_piece(
play.from,
player,
false,
play.to
);
this.pools[this.turn & 1].pieces[play.from] -= 1;
@ -431,7 +406,7 @@ GAME.Game = class {
let piece_id = this.board.tiles[play.from].piece;
if(piece_id !== null) {
let piece = this.board.pieces[piece_id];
if(piece.player == player) {
if(piece.player == player || !this.config.rules.turn) {
let moves = this.movement_tiles(piece, play.from);
for(let move of moves) {
if(move.tile == play.to && move.valid) {
@ -874,10 +849,6 @@ GAME.Const = {
Heart: 7,
},
Count: {
Pieces:40,
},
Piece: [
new GAME.GamePiece(
"Militia",
@ -1039,6 +1010,6 @@ GAME.Const = {
},
};
GAME.init = () => {
GAME_DATA = new GAME.Game();
GAME.init = (config) => {
GAME_DATA = new GAME.Game(config);
};

View File

@ -2,18 +2,81 @@ class GameConfig {
constructor() {
this.key = new Uint8Array(4);
this.name = "";
//this.user = 0;
//this.user = null;
this.pieces = [ ];
this.layout = [ ];
this.pools = new Uint8Array(14);
this.layout = new GameConfig.Layout();
this.pool = new Uint8Array(14);
this.actions = [ ];
this.states = [ ];
this.rules = {
turn:true,
};
//this.actions = [ ];
//this.states = [ ];
}
add_piece(piece) {
this.pieces.push(piece);
return this;
}
set_pieces(pieces) {
this.pieces = pieces;
return this;
}
set_layout(layout) {
this.layout = layout;
return this;
}
set_pool(pool) {
this.pool = pool;
return this;
}
set_rule(rule, value) {
this.rules[rule] = value;
return this;
}
count_pieces() {
let count = this.layout.pieces.length;
for(let i = 0; i < this.pool.length; ++i) {
count += this.pool[i];
}
return count;
}
}
GameConfig.State = class {
//this.name = "";
};
GameConfig.Layout = class {
constructor() {
this.pieces = [ ];
}
add_piece(piece_id, player, promote, hex) {
this.pieces.push({
piece:piece_id,
player:player,
promote:promote,
hex:hex,
});
return this;
}
mirror(player) {
let length = this.pieces.length;
for(let i = 0; i < length; ++i) {
this.pieces.push({
piece:this.pieces[i].piece,
player:player,
promote:this.pieces[i].promote,
hex:new MATH.Vec2(8 - this.pieces[i].hex.x, 8 - this.pieces[i].hex.y),
});
}
return this;
}
}

280
www/js/game_config_const.js Normal file
View File

@ -0,0 +1,280 @@
const CONFIG_PIECES_STANDARD = [
new GAME.GamePiece(
"Militia",
new GAME.PieceMovement()
.add(0)
.add(1)
.add(5),
new GAME.PieceMovement()
.add(0)
.add(1)
.add(2)
.add(3)
.add(4)
.add(5),
),
new GAME.GamePiece(
"Lance",
new GAME.PieceMovement()
.add_stride(0)
.add(1)
.add(3)
.add(5),
new GAME.PieceMovement()
.add(0)
.add_stride(1, 1)
.add_stride(2, 1)
.add(3)
.add_stride(4, 1)
.add_stride(5, 1),
),
new GAME.GamePiece(
"Knight",
new GAME.PieceMovement()
.add(6)
.add(8)
.add(9)
.add(11)
.add(13)
.add(14)
.add(16)
.add(17),
new GAME.PieceMovement()
.add(6)
.add(8)
.add(9)
.add(11)
.add(13)
.add(14)
.add(16)
.add(17)
.add_alt(1),
),
new GAME.GamePiece(
"Tower",
new GAME.PieceMovement()
.add(0)
.add(1)
.add(3)
.add(5)
.add(6)
.add(11),
new GAME.PieceMovement()
.add(0)
.add(1)
.add(2)
.add(3)
.add(4)
.add(5)
.add(6)
.add(8)
.add(9)
.add(11),
),
new GAME.GamePiece(
"Castle",
new GAME.PieceMovement()
.add(0)
.add(1)
.add(2)
.add(4)
.add(5)
.add(7)
.add(10),
new GAME.PieceMovement()
.add(0)
.add(1)
.add(2)
.add(3)
.add(4)
.add(5)
.add(7)
.add(10)
.add_alt(2),
),
new GAME.GamePiece(
"Dragon",
new GAME.PieceMovement()
.add_stride(6)
.add_stride(7)
.add_stride(8)
.add_stride(9)
.add_stride(10)
.add_stride(11),
new GAME.PieceMovement()
.add(0)
.add(1)
.add(2)
.add(3)
.add(4)
.add(5)
.add_stride(6)
.add_stride(7)
.add_stride(8)
.add_stride(9)
.add_stride(10)
.add_stride(11),
),
new GAME.GamePiece(
"Behemoth",
new GAME.PieceMovement()
.add_stride(0)
.add_stride(1)
.add_stride(2)
.add_stride(3)
.add_stride(4)
.add_stride(5),
new GAME.PieceMovement()
.add_stride(0)
.add_stride(1)
.add_stride(2)
.add_stride(3)
.add_stride(4)
.add_stride(5)
.add(12)
.add(13)
.add(14)
.add(15)
.add(16)
.add(17)
),
new GAME.GamePiece(
"Heart",
new GAME.PieceMovement()
.add(0)
.add(1)
.add(2)
.add(3)
.add(4)
.add(5)
.add(7)
.add(10),
),
];
const CONFIG_LAYOUT_STANDARD = new GameConfig.Layout()
.add_piece(0, 0, false, new MATH.Vec2(0, 1))
.add_piece(0, 0, false, new MATH.Vec2(1, 1))
.add_piece(0, 0, false, new MATH.Vec2(2, 2))
.add_piece(0, 0, false, new MATH.Vec2(3, 2))
.add_piece(0, 0, false, new MATH.Vec2(4, 3))
.add_piece(0, 0, false, new MATH.Vec2(5, 3))
.add_piece(0, 0, false, new MATH.Vec2(6, 4))
.add_piece(0, 0, false, new MATH.Vec2(7, 4))
.add_piece(0, 0, false, new MATH.Vec2(8, 5))
.add_piece(1, 0, false, new MATH.Vec2(0, 0))
.add_piece(1, 0, false, new MATH.Vec2(8, 4))
.add_piece(2, 0, false, new MATH.Vec2(1, 0))
.add_piece(2, 0, false, new MATH.Vec2(7, 3))
.add_piece(3, 0, false, new MATH.Vec2(3, 0))
.add_piece(3, 0, false, new MATH.Vec2(5, 1))
.add_piece(4, 0, false, new MATH.Vec2(2, 0))
.add_piece(4, 0, false, new MATH.Vec2(6, 2))
.add_piece(5, 0, false, new MATH.Vec2(4, 2))
.add_piece(6, 0, false, new MATH.Vec2(4, 1))
.add_piece(7, 0, false, new MATH.Vec2(4, 0))
.mirror(1);
const CONFIG_POOL_DEMO = new Uint8Array([6, 4, 4, 4, 4, 2, 2, 6, 4, 4, 4, 4, 2, 2]);
const GAME_CONFIGS = {
// Standard
Standard: new GameConfig()
.set_pieces(CONFIG_PIECES_STANDARD)
.set_layout(CONFIG_LAYOUT_STANDARD),
// Militia
Guide_Militia: new GameConfig()
.set_pieces(CONFIG_PIECES_STANDARD)
.set_layout(
new GameConfig.Layout()
.add_piece(0, 0, false, new MATH.Vec2(4, 3))
.add_piece(0, 0, true, new MATH.Vec2(4, 5))
.add_piece(0, 0, false, new MATH.Vec2(0, 0))
.add_piece(0, 0, true, new MATH.Vec2(0, 4))
)
.set_pool(CONFIG_POOL_DEMO)
.set_rule("turn", false),
// Lance
Guide_Lance: new GameConfig()
.set_pieces(CONFIG_PIECES_STANDARD)
.set_layout(
new GameConfig.Layout()
.add_piece(1, 0, false, new MATH.Vec2(4, 3))
.add_piece(1, 0, true, new MATH.Vec2(4, 5))
)
.set_pool(CONFIG_POOL_DEMO)
.set_rule("turn", false),
// Knight
Guide_Knight: new GameConfig()
.set_pieces(CONFIG_PIECES_STANDARD)
.set_layout(
new GameConfig.Layout()
.add_piece(2, 0, false, new MATH.Vec2(4, 3))
.add_piece(2, 0, true, new MATH.Vec2(4, 5))
)
.set_pool(CONFIG_POOL_DEMO)
.set_rule("turn", false),
// Tower
Guide_Tower: new GameConfig()
.set_pieces(CONFIG_PIECES_STANDARD)
.set_layout(
new GameConfig.Layout()
.add_piece(3, 0, false, new MATH.Vec2(4, 3))
.add_piece(3, 0, true, new MATH.Vec2(4, 5))
.add_piece(4, 0, false, new MATH.Vec2(5, 4))
.add_piece(2, 0, false, new MATH.Vec2(5, 7))
)
.set_pool(CONFIG_POOL_DEMO)
.set_rule("turn", false),
// Castle
Guide_Castle: new GameConfig()
.set_pieces(CONFIG_PIECES_STANDARD)
.set_layout(
new GameConfig.Layout()
.add_piece(4, 0, false, new MATH.Vec2(4, 3))
.add_piece(4, 0, true, new MATH.Vec2(4, 5))
)
.set_pool(CONFIG_POOL_DEMO)
.set_rule("turn", false),
// Dragon
Guide_Dragon: new GameConfig()
.set_pieces(CONFIG_PIECES_STANDARD)
.set_layout(
new GameConfig.Layout()
.add_piece(5, 0, false, new MATH.Vec2(4, 3))
.add_piece(5, 0, true, new MATH.Vec2(4, 5))
)
.set_pool(CONFIG_POOL_DEMO)
.set_rule("turn", false),
// Behemoth
Guide_Behemoth: new GameConfig()
.set_pieces(CONFIG_PIECES_STANDARD)
.set_layout(
new GameConfig.Layout()
.add_piece(6, 0, false, new MATH.Vec2(4, 3))
.add_piece(6, 0, true, new MATH.Vec2(4, 5))
.add_piece(2, 1, false, new MATH.Vec2(5, 4))
.add_piece(2, 1, false, new MATH.Vec2(3, 4))
)
.set_pool(CONFIG_POOL_DEMO)
.set_rule("turn", false),
// Heart
Guide_Heart: new GameConfig()
.set_pieces(CONFIG_PIECES_STANDARD)
.set_layout(
new GameConfig.Layout()
.add_piece(7, 0, false, new MATH.Vec2(4, 4))
.add_piece(7, 1, false, new MATH.Vec2(3, 5))
.add_piece(1, 1, false, new MATH.Vec2(5, 7))
)
.set_pool(CONFIG_POOL_DEMO)
.set_rule("turn", false),
};

View File

@ -236,7 +236,7 @@ const INTERFACE = {
console.log("Select not null");
// Play selection.
if(INTERFACE_DATA.hover.source == 0 && (INTERFACE_DATA.mode == INTERFACE.Mode.Local || INTERFACE_DATA.player == (GAME_DATA.turn & 1))) {
if(INTERFACE_DATA.hover.source == 0 && (INTERFACE_DATA.mode == INTERFACE.Mode.Local || (INTERFACE_DATA.player == (GAME_DATA.turn & 1) || !GAME_DATA.config.rules.turn))) {
console.log("D1");
let tile_state = INTERFACE_DATA.Game.board_state[INTERFACE_DATA.hover.tile][1];
@ -560,7 +560,8 @@ const INTERFACE = {
let is_play = null;
if(GAME_DATA.turn > 0 && play.source != 0xF && (play.to == i || ((play.source == 0 || play.source == 2) && play.from == i))) {
is_play = +!(GAME_DATA.turn & 1);
let piece_id = GAME_DATA.board.tiles[play.to].piece;
is_play = GAME_DATA.board.pieces[piece_id].player;
}
let is_check = (GAME_DATA.state.check != 0 || GAME_DATA.state.code == GAME.Const.State.Checkmate)
&& piece !== null
@ -603,7 +604,7 @@ const INTERFACE = {
border_color = INTERFACE.Color.HintHover;
} else if(background_color == null) {
if(INTERFACE_DATA.select === null && is_play !== null) {
if((GAME_DATA.turn & 1) != GAME.Const.Player.Dawn) { border_color = INTERFACE.Color.DawnDark; }
if(is_play == GAME.Const.Player.Dawn) { border_color = INTERFACE.Color.DawnDark; }
else { border_color = INTERFACE.Color.DuskDark; }
background_scale = 0.9;
}
@ -1129,7 +1130,7 @@ const INTERFACE = {
}
// Draw border
let turn_indicator = player == (GAME_DATA.turn & 1) && (INTERFACE_DATA.player == player || INTERFACE_DATA.player == 2);
let turn_indicator = !GAME_DATA.config.rules.turn || (player == (GAME_DATA.turn & 1) && (INTERFACE_DATA.player == player || INTERFACE_DATA.player == 2));
if(is_hover || background_color !== null || turn_indicator) {
if(is_hover) { this.ctx.fillStyle = INTERFACE.Color.HintHover; }
else { this.ctx.fillStyle = player_color; }
@ -1194,8 +1195,8 @@ const INTERFACE = {
},
},
init(token, mode, params={}) {
GAME.init();
init(token, config, mode, params={}) {
GAME.init(config);
let player = 2;
@ -1304,6 +1305,11 @@ const INTERFACE = {
}
},
load(config) {
GAME.init(config);
INTERFACE.reset();
},
uninit() {
if(INTERFACE_DATA !== null) {
if(INTERFACE_DATA.mode != INTERFACE.Mode.Local) {
@ -1485,7 +1491,7 @@ const INTERFACE = {
case 2: {
let piece_id = GAME_DATA.board.tiles[play.from].piece;
let piece = GAME_DATA.board.pieces[piece_id];
valid = piece.player == (GAME_DATA.turn & 1);
valid = piece.player == (GAME_DATA.turn & 1) || !GAME_DATA.config.rules.turn;
} break;
case 1: {
@ -1586,7 +1592,7 @@ const INTERFACE = {
if(turn >= 0 && turn <= INTERFACE_DATA.Game.history.length) {
if(turn <= INTERFACE_DATA.Replay.turn) {
INTERFACE_DATA.Replay.turn = 0;
GAME.init();
GAME.init(GAME_DATA.config);
}
let play = null;

View File

@ -649,35 +649,54 @@ const SCENES = {
},
Guide:class{
constructor() { }
constructor() {
this.game = null;
}
load() {
UI.mainmenu("guide");
let buttons_bottom = [ ];
buttons_bottom.push(UI.button(LANG("undo"), () => { INTERFACE.undo(); }));
buttons_bottom.push(UI.button(LANG("reset"), () => { INTERFACE.reset(); }));
buttons_bottom.push(UI.button(LANG("back"), () => { SCENE.load(SCENES.Browse); }));
UI.nav([ ], buttons_bottom);
UI.mainnav([
UI.button("Game", () => { SCENE.refresh("game.html"); }),
UI.button("Pieces", () => { SCENE.refresh("pieces.html"); }),
UI.button("Interface", () => { SCENE.refresh("interface.html"); }),
UI.button_piece(0, () => {
INTERFACE.load(GAME_CONFIGS.Guide_Militia);
}),
UI.button_piece(1, () => {
INTERFACE.load(GAME_CONFIGS.Guide_Lance);
}),
UI.button_piece(2, () => {
INTERFACE.load(GAME_CONFIGS.Guide_Knight);
}),
UI.button_piece(3, () => {
INTERFACE.load(GAME_CONFIGS.Guide_Tower);
}),
UI.button_piece(4, () => {
INTERFACE.load(GAME_CONFIGS.Guide_Castle);
}),
UI.button_piece(5, () => {
INTERFACE.load(GAME_CONFIGS.Guide_Dragon);
}),
UI.button_piece(6, () => {
INTERFACE.load(GAME_CONFIGS.Guide_Behemoth);
}),
UI.button_piece(7, () => {
INTERFACE.load(GAME_CONFIGS.Guide_Heart);
}),
], []);
let body = document.createElement("article");
body.setAttribute("id", "article");
body.setAttribute("class", "text");
UI.maincontent(body);
let canvas = document.createElement("canvas");
canvas.setAttribute("id", "game");
MAIN.appendChild(canvas);
this.refresh("game.html");
INTERFACE.init(null, GAME_CONFIGS.Guide_Militia, INTERFACE.Mode.Local, {});
history.pushState(null, "Dzura - Guide", "/guide/");
return true;
}
refresh(page) {
fetch("/guide/" + page)
.then((response) => {
return response.text();
})
.then((text) => {
let parser = new DOMParser();
let body = parser.parseFromString(text, "text/html");
document.getElementById("article").innerHTML = body.body.innerHTML;
});
unload() {
INTERFACE.uninit();
}
},
@ -1080,7 +1099,7 @@ const SCENES = {
MAIN.appendChild(canvas);
// Interface
INTERFACE.init(this.token, this.mode, {
INTERFACE.init(this.token, GAME_CONFIGS.Standard, this.mode, {
rotate:this.rotate,
});
if(this.turn !== null) {
@ -1146,7 +1165,7 @@ const SCENES = {
};
}
INTERFACE.init(data, INTERFACE.Mode.Local, params);
INTERFACE.init(data, GAME_CONFIGS.Standard, INTERFACE.Mode.Local, params);
if(data !== null) {
for(let i = 0; i < data.turn; ++i) {
INTERFACE_DATA.Game.history_begin.push(data.history[i]);

View File

@ -25,6 +25,16 @@ const UI = {
return button;
},
button_piece(piece, callback=null, select=false) {
let button = document.createElement("button");
let moves = document.createElement("canvas");
button.appendChild(moves);
setTimeout(UI.draw_play_icons, 10, moves, [piece]);
if(select) { button.setAttribute("class", "selected"); }
if(callback !== null) { button.addEventListener("click", callback); }
return button;
},
submit(text) {
let button = document.createElement("input");
button.setAttribute("type", "submit");