$ npm install
$ npm run
interface IGameState {
cells: { value: string; modifiable: boolean }[];
}
class Game extends React.Component<Record<string, unknown>, IGameState> {
constructor(props: Record<string, unknown>) {
super(props);
const cells = new Array(81)
.fill(null)
.map(() => ({ value: "", modifiable: false}));
this.state = {
cells: cells
};
}
componentDidMount(): void {
this.initFrom(generateStaticGrid());
}
initFrom(values: string): void {
assert.ok(values.length === 81);
const cells = this.state.cells;
for (let index = 0; index < 81; index++) {
cells[index].value = values[index] === "." ? "" : values[index];
cells[index].modifiable = values[index] === "." ? true : false;
}
this.setState({ cells: cells });
}
}
reset(): void {
const cells = this.state.cells;
for (let index = 0; index < 81; index++) {
if (cells[index].modifiable) {
cells[index].value = "";
}
}
this.setState({ cells: cells });
}
handleChange(index: number, value: string): void {
assert.ok(value === "" || (Number(value) >= 1 && Number(value) <= 9));
assert.ok(index >= 0 && index < 81);
if (!this.state.cells[index].modifiable) {
console.error(
"Trying to change an non modifiable cell. Should not happend"
);
}
const cells = this.state.cells;
cells[index].value = value;
this.setState({ cells: cells });
}
render(): JSX.Element {
return (
<div className="sudoku">
<div>
<button onClick={this.reset.bind(this)}>Reset</button>
</div>
<br />
<Grid
cells={this.state.cells}
onChange={(index: number, value: string) =>
this.handleChange(index, value)
}
/>
</div> );
}
interface IGridProps {
cells: { value: string; modifiable: boolean }[];
onChange: (index: number, value: string) => void;
}
class Grid extends React.Component<IGridProps> {
errors: boolean[] = new Array(81).fill(false);
handleChange(index: number, value: string): void {
this.props.onChange(index, value);
}
renderCell(index: number): JSX.Element {
assert.ok(index >= 0 && index < 81);
return (
<Cell
index={index}
value={this.props.cells[index].value}
onChange={
this.props.cells[index].modifiable
? (index: number, value: string) => this.handleChange(index, value)
: null
}
error={this.errors[index]}
/> );
}
renderBlock(blockNum: number): JSX.Element {
assert.ok(blockNum >= 0 && blockNum < 9);
const index = cellsIndexOfBlock(blockNum);
return (
<td>
{this.renderCell(index[0])}
{this.renderCell(index[1])}
{this.renderCell(index[2])}
<br />
{this.renderCell(index[3])}
{this.renderCell(index[4])}
{this.renderCell(index[5])}
<br />
{this.renderCell(index[6])}
{this.renderCell(index[7])}
{this.renderCell(index[8])}
</td> )
}
render(): JSX.Element {
const isFinished = this.checkAll();
return (
<div>
<table className="grid">
<tbody>
{[0, 1, 2].map((line) => (
<tr key={line.toString()}>
{this.renderBlock(line * 3)}
{this.renderBlock(line * 3 + 1)}
{this.renderBlock(line * 3 + 2)}
</tr>
))}
</tbody>
</table>
{isFinished && (
<h2 className="status" id="status">
Sudoku completed
</h2>
)}
</div> );
}
}
interface ICellProps {
index: number;
value: string;
onChange: ((index: number, value: string) => void) | null;
error: boolean;
}
onChange(event: React.ChangeEvent): void {
if (this.props.onChange === null) {
return;
}
if (event.target.value === "" || validInput.test(event.target.value)) {
this.props.onChange(this.props.index, event.target.value);
}
}
render(): JSX.Element {
let cellClass = "";
if (this.props.value.length > 1) {
cellClass += "mv ";
}
if (this.props.onChange === null) {
cellClass += "locked ";
}
if (this.props.error) {
cellClass += "wrongvalue ";
}
return (
<input
id={String(this.props.index)}
className={cellClass}
maxLength={1}
value={this.props.value}
onChange={(event) => this.onChange(event)}
readOnly={this.props.onChange === null}
/> );
}
$ docker run -d --name couchdb -p 5984:5984 -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password couchdb
Alternatively take a look at the README.md file of C-Service.
$ export COUCHDB_USER=admin COUCHDB_PASSWORD=password
$ npx @concordant/c-service
npm install
The service worker is bundled in `dist/c-service-worker.js`.
To use it, just copy (or link) it to the public repository of the application i.e. public/c-service-worker.js.
$ npm i @concordant/c-client
import { client } from '@concordant/c-client';
import CONFIG from "../config.json";
const session = client.Session.Companion.connect(
CONFIG.dbName,
CONFIG.serviceUrl,
CONFIG.credentials
);
const collection = session.openCollection("sudoku", false);
interface IGameState {
mvmap: any;
cells: { value: string; modifiable: boolean }[];
}
constructor(props: Record) {
super(props);
const cells = new Array(81)
.fill(null)
.map(() => ({ value: "", modifiable: false }));
const mvmap = collection.open("grid", "MVMap", false, this.handler.bind(this));
this.state = {
mvmap: mvmap,
cells: cells
};
}
It is important to remember that the app operates on objects within a transaction, i.e., a block of related accesses. You are not allowed to operate on objects outside of a transaction. Note: Concordant will eventually support a choice of transaction consistency levels (a.k.a. isolation levels). However, in the currently available alpha version, we only implement None, i.e., a transaction is not guaranteed to be atomic and cannot abort.
reset(): void {
const cells = this.state.cells;
session.transaction(client.utils.ConsistencyLevel.None, () => {
for (let index = 0; index < 81; index++) {
if (cells[index].modifiable) {
cells[index].value = "";
this.state.mvmap.setString(index, cells[index].value);
}
}
});
this.setState({ cells: cells });
}
handleChange(index: number, value: string): void {
assert.ok(value === "" || (Number(value) >= 1 && Number(value) <= 9));
assert.ok(index >= 0 && index < 81);
if (!this.state.cells[index].modifiable) {
console.error("Trying to change an non modifiable cell. Should not happend");
}
const cells = this.state.cells;
cells[index].value = value;
this.setState({ cells: cells });
session.transaction(client.utils.ConsistencyLevel.None, () => {
this.state.mvmap.setString(index, value);
});
}
function hashSetToString(set: any): string {
const res = new Set();
const it = set.iterator();
while (it.hasNext()) {
const val = it.next();
if (val !== "") {
res.add(val);
}
}
return Array.from(res).sort().join(" ");
}
componentDidMount(): void {
this.initFrom(generateStaticGrid());
}
private handler() {
this.pullGrid()
}
pullGrid(): void {
const cells = this.state.cells;
collection.pull(client.utils.ConsistencyLevel.None);
for (let index = 0; index < 81; index++) {
if (cells[index].modifiable) {
cells[index].value = "";
}
}
session.transaction(client.utils.ConsistencyLevel.None, () => {
const itString = this.state.mvmap.iteratorString();
while (itString.hasNext()) {
const val = itString.next();
cells[val.first].value = hashSetToString(val.second);
}
});
this.setState({ cells: cells });
}
$ npm run