diff --git a/assets/game/suika/fruit-1.png b/assets/game/suika/fruit-1.png deleted file mode 100644 index dd87dbdb9..000000000 Binary files a/assets/game/suika/fruit-1.png and /dev/null differ diff --git a/assets/game/suika/fruit-10.png b/assets/game/suika/fruit-10.png deleted file mode 100644 index fbcc86681..000000000 Binary files a/assets/game/suika/fruit-10.png and /dev/null differ diff --git a/assets/game/suika/fruit-11.png b/assets/game/suika/fruit-11.png deleted file mode 100644 index a431e0e41..000000000 Binary files a/assets/game/suika/fruit-11.png and /dev/null differ diff --git a/assets/game/suika/fruit-2.png b/assets/game/suika/fruit-2.png deleted file mode 100644 index 7534abd90..000000000 Binary files a/assets/game/suika/fruit-2.png and /dev/null differ diff --git a/assets/game/suika/fruit-3.png b/assets/game/suika/fruit-3.png deleted file mode 100644 index 3de5f7cea..000000000 Binary files a/assets/game/suika/fruit-3.png and /dev/null differ diff --git a/assets/game/suika/fruit-4.png b/assets/game/suika/fruit-4.png deleted file mode 100644 index 199fe977e..000000000 Binary files a/assets/game/suika/fruit-4.png and /dev/null differ diff --git a/assets/game/suika/fruit-5.png b/assets/game/suika/fruit-5.png deleted file mode 100644 index 0bf87ee0a..000000000 Binary files a/assets/game/suika/fruit-5.png and /dev/null differ diff --git a/assets/game/suika/fruit-6.png b/assets/game/suika/fruit-6.png deleted file mode 100644 index 24f621746..000000000 Binary files a/assets/game/suika/fruit-6.png and /dev/null differ diff --git a/assets/game/suika/fruit-7.png b/assets/game/suika/fruit-7.png deleted file mode 100644 index a215fc67c..000000000 Binary files a/assets/game/suika/fruit-7.png and /dev/null differ diff --git a/assets/game/suika/fruit-8.png b/assets/game/suika/fruit-8.png deleted file mode 100644 index 43aaa49db..000000000 Binary files a/assets/game/suika/fruit-8.png and /dev/null differ diff --git a/assets/game/suika/fruit-9.png b/assets/game/suika/fruit-9.png deleted file mode 100644 index df484539a..000000000 Binary files a/assets/game/suika/fruit-9.png and /dev/null differ diff --git a/assets/game/wordle/all.json b/assets/game/wordle/all.json deleted file mode 100644 index 2a88f751b..000000000 --- a/assets/game/wordle/all.json +++ /dev/null @@ -1 +0,0 @@ -["ABASE","ABASH","ABATE","ABHOR","ABIDE","ABORT","ABOUT","ABOVE","ABUSE","ACORN","ACRID","ACTOR","ACUTE","ADAGE","ADAPT","ADDLE","ADEPT","ADLIB","ADMIT","ADOBE","ADOPT","ADORE","ADORN","ADULT","AEGIS","AFFIX","AFTER","AGAIN","AGAPE","AGENT","AGILE","AGING","AGONY","AGREE","AHEAD","AISLE","ALARM","ALBUM","ALERT","ALIAS","ALIBI","ALIEN","ALIGN","ALIKE","ALIVE","ALLAY","ALLEY","ALLOT","ALLOW","ALLOY","ALOFT","ALONE","ALONG","ALOOF","ALOUD","ALTER","AMASS","AMAZE","AMBLE","AMEND","AMINO","AMISS","AMITY","AMONG","AMPLE","AMUSE","ANGEL","ANGER","ANGLE","ANGRY","ANKLE","ANNEX","ANNOY","ANNUL","ANTIC","ANVIL","AORTA","APART","APPAL","APPLE","APPLY","APRIL","APRON","APTLY","ARDOR","ARENA","ARGOT","ARGUE","ARISE","ARMOR","AROMA","ARRAY","ARROW","ARSON","ASIDE","ASKEW","ASPEN","ASSAY","ASSET","ATLAS","ATONE","ATTIC","AUDIO","AUDIT","AUGER","AUGUR","AVAIL","AVERT","AVOID","AWAIT","AWAKE","AWARD","AWARE","AWFUL","AXIOM","AZURE","BACON","BADGE","BADLY","BALKY","BALMY","BANAL","BANDY","BARGE","BARON","BASIC","BASIN","BASIS","BASTE","BATCH","BATHE","BATON","BAWDY","BEACH","BEARD","BEAST","BEGET","BEGIN","BEING","BELCH","BELIE","BELLY","BELOW","BENCH","BERRY","BESET","BESOT","BIBLE","BIGOT","BIPED","BIRTH","BISON","BITCH","BLACK","BLADE","BLAME","BLAND","BLANK","BLARE","BLAST","BLAZE","BLEAK","BLEED","BLEND","BLESS","BLIND","BLINK","BLISS","BLOCK","BLOND","BLOOD","BLOOM","BLUFF","BLUNT","BLURB","BLURT","BLUSH","BOARD","BOAST","BOGUS","BONNY","BONUS","BOOST","BOOTH","BOTCH","BOUGH","BOUND","BOWEL","BOWER","BRACE","BRAID","BRAIN","BRAKE","BRAND","BRASH","BRASS","BRAVE","BRAWL","BREAD","BREAK","BREED","BRIBE","BRICK","BRIDE","BRIEF","BRING","BRINK","BRISK","BROAD","BROIL","BROKE","BROOD","BROOK","BROOM","BROWN","BRUIT","BRUNT","BRUSH","BRUTE","BUDDY","BUDGE","BUGGY","BUILD","BULGE","BULKY","BULLY","BUNCH","BURST","BUSHY","BUXOM","BUYER","CABAL","CABIN","CABLE","CACHE","CADET","CADGE","CAMEL","CAMEO","CANAL","CANDY","CANNY","CANOE","CANON","CANTO","CAPER","CARAT","CARGO","CAROL","CARRY","CARVE","CASTE","CATCH","CATER","CAULK","CAUSE","CAVIL","CEASE","CELLO","CHAFE","CHAFF","CHAIN","CHAIR","CHALK","CHAMP","CHANT","CHAOS","CHARM","CHART","CHARY","CHASE","CHASM","CHEAP","CHEAT","CHECK","CHEEK","CHEER","CHESS","CHEST","CHIDE","CHIEF","CHILD","CHILL","CHINA","CHIRP","CHOIR","CHOKE","CHORD","CHORE","CHUCK","CHUNK","CHURL","CIGAR","CIVIC","CIVIL","CLAIM","CLAMP","CLASH","CLASP","CLASS","CLEAN","CLEAR","CLEFT","CLERK","CLICK","CLIFF","CLIMB","CLING","CLOAK","CLOCK","CLONE","CLOSE","CLOTH","CLOUD","CLOUT","CLOWN","CLUMP","COACH","COAST","COLON","COLOR","COMET","COMIC","COMMA","CORAL","CORNY","CORPS","COUCH","COUGH","COULD","COUNT","COURT","COVEN","COVER","COVET","COWER","COZEN","CRACK","CRAFT","CRAMP","CRANE","CRASH","CRASS","CRATE","CRAVE","CRAWL","CRAZE","CRAZY","CREAM","CREDO","CREED","CREEK","CREEP","CREST","CRIME","CRIMP","CRISP","CROAK","CROOK","CROON","CROSS","CROWD","CROWN","CRUDE","CRUEL","CRUMB","CRUSH","CRUST","CRYPT","CUBIC","CURSE","CURVE","CYCLE","CYNIC","DADDY","DAILY","DAIRY","DALLY","DANCE","DANDY","DATED","DATUM","DAUNT","DEATH","DEBAR","DEBUT","DECAY","DECOY","DECRY","DEFER","DEIFY","DEIGN","DEITY","DELAY","DELTA","DELVE","DEMUR","DENIM","DENSE","DEPOT","DEPTH","DETER","DEVIL","DIARY","DIGIT","DINER","DINGY","DIRGE","DIRTY","DISCO","DITCH","DITTY","DIVER","DIZZY","DODGE","DOGGO","DOGMA","DONOR","DOUBT","DOUGH","DOUSE","DOWDY","DOWRY","DOWSE","DOYEN","DOZEN","DRAFT","DRAIN","DRAMA","DRAWL","DRAWN","DREAD","DREAM","DREGS","DRESS","DRIFT","DRILL","DRINK","DRIVE","DROLL","DRONE","DROOL","DROOP","DROSS","DROVE","DROWN","DRUNK","DRYER","DUMMY","DUNCE","DUSKY","DUSTY","DWARF","DWELL","DYING","EAGER","EAGLE","EARLY","EARTH","EASEL","ECLAT","EDICT","EDIFY","EERIE","EIGHT","EJECT","ELBOW","ELDER","ELECT","ELEGY","ELITE","ELUDE","EMAIL","EMBED","EMEND","EMOTE","EMPTY","ENACT","ENDOW","ENDUE","ENEMY","ENJOY","ENNUI","ENROL","ENSUE","ENTER","ENTRY","ENVOY","EPOCH","EQUAL","EQUIP","ERASE","ERECT","ERODE","ERROR","ERUPT","ESSAY","ETHIC","ETHOS","EVADE","EVENT","EVERY","EVICT","EVOKE","EXACT","EXALT","EXCEL","EXERT","EXILE","EXIST","EXPEL","EXTOL","EXTRA","EXUDE","EXULT","FABLE","FACET","FAINT","FAIRY","FAITH","FALSE","FANCY","FARCE","FATAL","FAULT","FAUNA","FAVOR","FEAST","FEIGN","FEINT","FELON","FENCE","FERAL","FERRY","FETCH","FETID","FETUS","FEVER","FIBER","FIBRE","FIELD","FIEND","FIFTH","FIFTY","FIGHT","FILCH","FILLY","FILTH","FINAL","FINCH","FIRST","FISHY","FIXED","FLAIL","FLAIR","FLAKE","FLAME","FLANK","FLARE","FLASH","FLASK","FLECK","FLEET","FLESH","FLICK","FLING","FLINT","FLIRT","FLOAT","FLOCK","FLOOD","FLOOR","FLORA","FLOUR","FLOUT","FLUID","FLUKE","FLUNK","FLUSH","FOCUS","FOGGY","FOLLY","FORAY","FORCE","FORGE","FORGO","FORTE","FORTH","FORTY","FORUM","FOSSE","FOUND","FOYER","FRAIL","FRAME","FRANK","FRAUD","FREAK","FRESH","FRISK","FROND","FRONT","FROST","FROWN","FRUIT","FUNGI","FUNKY","FUNNY","FUROR","FUSSY","FUSTY","FUZZY","GABBY","GAFFE","GAMUT","GAUDY","GAUGE","GAUNT","GAVEL","GAWKY","GENRE","GENUS","GHOST","GIANT","GIDDY","GIRTH","GIVEN","GLADE","GLAND","GLARE","GLASS","GLAZE","GLEAM","GLEAN","GLIDE","GLOAT","GLOBE","GLOOM","GLORY","GLOSS","GLOVE","GNOME","GOODS","GOOSE","GORGE","GOUGE","GOURD","GRACE","GRADE","GRAFT","GRAIN","GRAND","GRANT","GRAPE","GRAPH","GRASP","GRASS","GRATE","GRAVE","GRAZE","GREAT","GREED","GREEN","GREET","GRIEF","GRILL","GRIND","GRIPE","GROAN","GROOM","GROPE","GROSS","GROUP","GROVE","GROWL","GRUFF","GUARD","GUESS","GUEST","GUIDE","GUILE","GUILT","GUISE","GULCH","GULLY","GUSTO","HABIT","HAIRY","HANDY","HAPPY","HARDY","HARRY","HARSH","HASTE","HASTY","HATCH","HAUNT","HAVEN","HAVOC","HEADY","HEART","HEAVE","HEAVY","HEDGE","HELLO","HELOT","HELVE","HENCE","HILLY","HINGE","HOARD","HOARY","HOBBY","HOIST","HONEY","HONOR","HORSE","HOTEL","HOUND","HOUSE","HOVEL","HOVER","HUFFY","HUMAN","HUMID","HUMOR","HUNCH","HURRY","HUSKY","ICING","IDEAL","IDIOM","IDIOT","IDYLL","IMAGE","IMBUE","IMPEL","IMPLY","INANE","INCUR","INDEX","INEPT","INERT","INFER","INNER","INPUT","INTER","INURE","IRATE","IRONY","ISSUE","IVORY","JADED","JAUNT","JEANS","JELLY","JEWEL","JOINT","JOLLY","JUDGE","JUICE","JUICY","JUMPY","JUROR","KIOSK","KNACK","KNAVE","KNEAD","KNEEL","KNIFE","KNOCK","KNOLL","KUDOS","LABEL","LABOR","LADLE","LAITY","LANCE","LAPSE","LARGE","LARVA","LASER","LASSO","LATCH","LATER","LATHE","LATIN","LAUGH","LAYER","LEACH","LEARN","LEASE","LEASH","LEAST","LEAVE","LEDGE","LEERY","LEGAL","LEMON","LEVEE","LEVEL","LEVER","LIBEL","LICIT","LIGHT","LIKEN","LIMBO","LIMIT","LINEN","LINER","LIPID","LITER","LITHE","LITRE","LIVER","LIVID","LOATH","LOBBY","LOCAL","LOCUS","LODGE","LOFTY","LOGIC","LOOSE","LORRY","LOVER","LOWER","LOYAL","LUCID","LUCKY","LUCRE","LUMEN","LUNAR","LUNCH","LURCH","LURID","LUSTY","LYNCH","LYRIC","MADAM","MAGIC","MAGMA","MAIZE","MAJOR","MANGY","MANIA","MANLY","MAPLE","MARCH","MARRY","MARSH","MASON","MATCH","MATHS","MATTE","MAUVE","MAVEN","MAXIM","MAYBE","MAYOR","MEALY","MEANS","MEATY","MEDAL","MEDIA","MELON","MERCY","MERGE","MERIT","MERRY","MESSY","METAL","METER","METRE","METRO","MIDST","MIGHT","MILKY","MIMIC","MINCE","MINOR","MINUS","MIRTH","MISER","MISTY","MIXER","MODEL","MODEM","MOGUL","MOIST","MOLAR","MOLDY","MOMMY","MONEY","MONTH","MOODY","MORAL","MORES","MORON","MOTEL","MOTIF","MOTOR","MOTTO","MOULD","MOUNT","MOURN","MOUSE","MOUTH","MOVIE","MUDDY","MUGGY","MULCT","MUNCH","MURAL","MURKY","MUSIC","MUSKY","MUSTY","MUTED","MUZZY","NADIR","NAIVE","NAKED","NASAL","NASTY","NATAL","NATTY","NAVAL","NEEDY","NERVE","NEVER","NEXUS","NICHE","NIECE","NIFTY","NIGHT","NINTH","NIPPY","NOBLE","NOISE","NOISY","NOMAD","NOOSE","NORTH","NOTCH","NOTED","NOVEL","NUDGE","NURSE","NYMPH","OASIS","OBESE","OCCUR","OCEAN","ODIUM","ODOUR","OFFER","OFTEN","OLIVE","ONION","ONSET","OPERA","OPINE","OPTIC","ORBIT","ORDER","ORGAN","OTHER","OTTER","OUNCE","OUTER","OVERT","OWNER","OXIDE","OZONE","PADDY","PAEAN","PAGAN","PAINT","PALMY","PANDA","PANEL","PANIC","PANTS","PAPER","PARCH","PARKA","PARRY","PARSE","PARTY","PASTA","PASTE","PATCH","PAUSE","PAVID","PEACE","PEACH","PEAKY","PEARL","PECAN","PEDAL","PEERY","PEEVE","PENAL","PENNY","PERCH","PERIL","PERKY","PESKY","PETAL","PETTY","PHASE","PHIAL","PHONE","PHONY","PHOTO","PIANO","PICKY","PIECE","PILOT","PINCH","PIOUS","PIQUE","PITCH","PITHY","PIVOT","PIZZA","PLACE","PLAIN","PLAIT","PLANE","PLANK","PLANT","PLATE","PLAZA","PLEAD","PLEAT","PLUCK","PLUMB","PLUME","PLUMP","PLUSH","POACH","POINT","POISE","POLAR","PORCH","POSIT","POSSE","POUND","POWER","PRANK","PRATE","PREEN","PRESS","PRICE","PRICK","PRIDE","PRIME","PRIMP","PRINT","PRIOR","PRIVY","PRIZE","PROBE","PRONE","PRONG","PROOF","PROSE","PROUD","PROVE","PROWL","PROXY","PRUDE","PRUNE","PSALM","PUDGY","PULSE","PUNCH","PUPIL","PURGE","PURSE","PUSHY","PYGMY","PYLON","QUACK","QUAFF","QUAIL","QUAKE","QUALM","QUART","QUASH","QUASI","QUEEN","QUEER","QUELL","QUERY","QUEST","QUEUE","QUICK","QUIET","QUILL","QUILT","QUIRK","QUITE","QUOTA","QUOTE","RABID","RADAR","RADIO","RAINY","RAISE","RALLY","RANCH","RANGE","RAPID","RASPY","RATIO","RAVEL","RAYON","RAZOR","REACH","REACT","READY","REALM","REBEL","REBUS","RECUR","REEDY","REFER","REGAL","REIGN","RELAX","RELAY","RELIC","REMIT","RENEW","REPAY","REPEL","REPLY","RESIN","RETCH","REVUE","RHYME","RIDER","RIDGE","RIFLE","RIGHT","RIGID","RIGOR","RINSE","RIPEN","RISKY","RITZY","RIVAL","RIVEN","RIVER","RIVET","ROAST","ROBOT","ROCKY","RODEO","ROUGH","ROUND","ROUSE","ROUTE","ROWDY","ROYAL","RUDDY","RULER","RUMOR","RUNIC","RURAL","RUSTY","SAINT","SALAD","SALLY","SALON","SALTY","SALVE","SATED","SAUCE","SAUCY","SAVOR","SAVVY","SCADS","SCALD","SCALE","SCAMP","SCANT","SCARE","SCARF","SCARP","SCARY","SCENE","SCENT","SCION","SCOFF","SCOLD","SCOOP","SCOPE","SCORE","SCORN","SCOUR","SCOUT","SCOWL","SCRAP","SCREW","SCRUB","SCUBA","SCUFF","SEAMY","SEINE","SEIZE","SENSE","SERVE","SEVEN","SEVER","SEWER","SHACK","SHADE","SHADY","SHAFT","SHAKE","SHAKY","SHALE","SHALL","SHAME","SHAPE","SHARD","SHARE","SHARK","SHARP","SHAVE","SHAWL","SHEAF","SHEAR","SHEEN","SHEEP","SHEER","SHEET","SHELF","SHELL","SHIFT","SHINE","SHINY","SHIRK","SHIRT","SHOAL","SHOCK","SHOOT","SHORE","SHORT","SHOUT","SHOVE","SHOWY","SHRED","SHRUB","SHRUG","SHUCK","SHUNT","SHYLY","SIBYL","SIDLE","SIEGE","SIEVE","SIGHT","SILKY","SILLY","SINCE","SINEW","SINGE","SIREN","SIXTY","SKATE","SKEIN","SKIFF","SKILL","SKIMP","SKIRT","SKULL","SKUNK","SLACK","SLAKE","SLANT","SLASH","SLATE","SLAVE","SLEEP","SLICE","SLICK","SLIDE","SLING","SLINK","SLOPE","SLOSH","SLOTH","SLUMP","SLURP","SMALL","SMART","SMASH","SMEAR","SMELL","SMILE","SMIRK","SMITE","SMOKE","SMOKY","SNACK","SNAIL","SNAKE","SNARE","SNARL","SNEAK","SNEER","SNIDE","SNIFF","SNIPE","SNOWY","SOBER","SOBRE","SOGGY","SOLAR","SOLID","SOLVE","SORRY","SOUND","SOUSE","SOUTH","SPACE","SPADE","SPANK","SPARE","SPARK","SPATE","SPAWN","SPEAK","SPEAR","SPECK","SPEED","SPELL","SPEND","SPICE","SPICY","SPIKE","SPILL","SPINE","SPINY","SPIRE","SPITE","SPLIT","SPOIL","SPOKE","SPOOF","SPOOL","SPOON","SPOOR","SPORT","SPOUT","SPRAY","SPRIG","SPUNK","SPURN","SPURT","SQUAB","SQUAD","SQUAT","SQUID","STACK","STAFF","STAGE","STAGY","STAID","STAIN","STAIR","STAKE","STALE","STALK","STALL","STAMP","STAND","STARE","STARK","START","STASH","STATE","STEAK","STEAL","STEAM","STEEL","STEEP","STEER","STERN","STICK","STIFF","STILL","STING","STINT","STOCK","STOIC","STOKE","STONE","STONY","STOOL","STOOP","STORE","STORM","STORY","STOUT","STOVE","STRAP","STRAW","STRAY","STREW","STRIP","STRUT","STUDY","STUFF","STUMP","STUNT","STYLE","SUGAR","SUITE","SULKY","SULLY","SUNNY","SUPER","SURGE","SURLY","SWAMP","SWANK","SWARM","SWEAR","SWEAT","SWEEP","SWEET","SWELL","SWIFT","SWILL","SWINE","SWING","SWIPE","SWIRL","SWOOP","SWORD","SYRUP","TABLE","TABOO","TACIT","TALLY","TALON","TANGO","TANGY","TAPER","TARDY","TARRY","TASTE","TASTY","TATTY","TAUNT","TEACH","TEASE","TEMPO","TEMPT","TENET","TENOR","TENSE","TEPID","TERSE","TESTY","THANK","THEFT","THEIR","THEME","THERE","THESE","THICK","THIEF","THIGH","THING","THINK","THIRD","THORN","THOSE","THREE","THROW","THUMB","THUMP","TIDAL","TIGER","TIGHT","TIMID","TINED","TINGE","TIRED","TITLE","TOADY","TOAST","TODAY","TOKEN","TONGS","TONIC","TOOTH","TOPIC","TORCH","TORSO","TOTAL","TOTEM","TOUCH","TOUGH","TOWEL","TOWER","TOXIC","TOXIN","TRACE","TRACK","TRACT","TRADE","TRAIL","TRAIN","TRAIT","TRAMP","TRASH","TRAWL","TREAD","TREAT","TREND","TRIAL","TRIBE","TRICK","TRITE","TROLL","TROOP","TRUCE","TRUCK","TRULY","TRUNK","TRUSS","TRUST","TRUTH","TUBER","TUMID","TUMOR","TUTOR","TWEAK","TWICE","TWINE","TWIST","ULCER","ULTRA","UNCLE","UNDER","UNIFY","UNION","UNITE","UNITY","UNTIL","UPPER","UPSET","URBAN","URINE","USAGE","USUAL","USURP","USURY","UTTER","VAGUE","VALID","VALOR","VALUE","VALVE","VAPID","VAPOR","VAULT","VENAL","VENOM","VENUE","VERGE","VERSE","VERVE","VIAND","VICAR","VIDEO","VIGOR","VIRAL","VIRUS","VISIT","VISTA","VITAL","VIVID","VOCAL","VOGUE","VOICE","VOMIT","VOTER","VOUCH","VYING","WACKY","WAGON","WAIST","WAIVE","WAKEN","WASTE","WATCH","WATER","WAVER","WEARY","WEAVE","WEDGE","WEIGH","WEIRD","WELSH","WHALE","WHARF","WHEAT","WHEEL","WHELM","WHELP","WHERE","WHICH","WHIFF","WHILE","WHINE","WHIRL","WHITE","WHOLE","WHOSE","WIDEN","WIDOW","WIDTH","WIELD","WINCE","WINDY","WISPY","WITCH","WITTY","WIZEN","WOMAN","WORLD","WORRY","WORSE","WORST","WORTH","WOULD","WOUND","WRATH","WREAK","WRECK","WREST","WRING","WRIST","WRITE","WRONG","WROTH","YACHT","YEARN","YEAST","YIELD","YOKEL","YOUNG","YOURS","YOUTH","YUMMY","ZESTY","ASIAN","AURAL","BOSOM","GREEK","INDIA","JAPAN","MINER","NEGRO","NEWLY","NYLON","OUGHT","ROMAN","SADLY","SANDY","SIXTH","SWISS","TENTH","ZEBRA","REBUT","SISSY","HUMPH","FOCAL","HEATH","KARMA","STINK","BATTY","FLOSS","HELIX","UNFED","OUTDO","SOWER","CLUCK","UNMET","BOOBY","SEEDY","FLUME","OFFAL","WOOER","BIOME","LAPEL","GONER","GOLEM","LOOPY","LYING","GAMMA","ISLET","MOULT","AGATE","FJORD","KEBAB","GUILD","ABACK","HYPER","DUTCH","TWEED","ENEMA","STEED","ABYSS","BOOZY","BRIAR","ALTAR","PULPY","DUCHY","GROIN","FIXER","ROGUE","HERON","VODKA","FINER","SURER","ROUGE","WROTE","TILDE","GRIME","USHER","TRIAD","RHINO","CONIC","MASSE","SONIC","USING","TAPIR","CRANK","ABBEY","SHIRE","WHACK","WRUNG","ROBIN","AGORA","PUPAL","TROVE","BLOKE","RUPEE","BRINE","SMELT","SAUTE","EPOXY","LOWLY","SNOUT","TROPE","FEWER","HEIST","SHOWN","FELLA","HOMER","BUTCH","SLUNG","TIPSY","GAMER","TIARA","CREPT","BAYOU","ATOLL","MANOR","CREAK","FROTH","PIETY","PAYER","PRIMO","BLOWN","CACAO","LOSER","BEADY","RETRO","HUTCH","PINTO","GAILY","EGRET","LILAC","FLUFF","HYDRO","FLACK","WENCH","STEAD","BERTH","ROOMY","BOBBY","APHID","TRYST","MIDGE","ELOPE","CINCH","STOMP","COYLY","UNFIT","PATTY","HUNKY","KHAKI","POKER","GRUEL","TWANG","UNLIT","WOVEN","OCTAL","RUDER","GAUZE","WHOOP","TIBIA","BOOZE","ALPHA","THYME","PARER","CHUTE","TRICE","SOOTH","RECAP","LIEGE","HOWDY","IONIC","UNSET","STEIN","SPIEL","MUMMY","WALTZ","PINEY","OMBRE","FANNY","BAKER","GLYPH","POOCH","HIPPY","LOUSE","GODLY","THREW","VALET","OPIUM","STUCK","RECUT","MULCH","MOCHA","SAFER","MOUND","UNDUE","SEDAN","GUSTY","UDDER","MANGA","MELEE","LEFTY","OCTET","RISEN","LEAKY","SPILT","MINTY","DERBY","SPELT","JIFFY","FILMY","CHOSE","NANNY","WOOZY","DITTO","STANK","DIODE","NINJA","SLEET","DOWEL","PALSY","BUILT","GOODY","PATIO","LADEN","LYMPH","RESET","CROCK","APING","TAMER","HATER","AWOKE","BRAWN","BIRCH","FREER","PLIER","WINCH","BORAX","NICER","TUNIC","PRIED","INLAY","CHUMP","LANKY","CRESS","EATER","KITTY","BOULE","VIGIL","CLUNG","CROUP","CLINK","FLIER","VOWEL","SNUCK","FLOWN","POPPY","CADDY","DUMPY","PALER","SWORE","REBAR","SPLAT","FLYER","HORNY","DOING","AMPLY","OVARY","FRITZ","TWIRL","LLAMA","EATEN","WHISK","REHAB","MACAW","SIGMA","SUSHI","ARBOR","GAYLY","BRAVO","STUNG","CUTIE","MATEY","BLUER","AIDER","SHOOK","BETEL","BONGO","BEGUN","GENIE","WRYLY","ROVER","WOKEN","DWELT","SMACK","HAZEL","BEECH","JETTY","DOLLY","JOKER","POSER","ANIME","LEAFY","STOOD","OUTGO","BILGE","HOTLY","MEANT","SHIED","DAISY","PUTTY","BURNT","TULIP","CRICK","VIXEN","GEEKY","STORK","AUNTY","FURRY","CRUMP","HAREM","SWORN","UNZIP","TROUT","POLYP","FATTY","FILET","SLIME","GLINT","SPORE","DIMLY","SUMAC","DONUT","STILT","SLIMY","DINGO","SEPIA","FRIAR","VILLA","SHANK","PIGGY","FECAL","ALGAE","RABBI","COPSE","SWOON","POUCH","ASCOT","VIOLA","RAJAH","DILLY","WOODY","SASSY","SAUNA","CLUED","SNUFF","FROCK","GONAD","HALVE","VINYL","UTILE","ASHEN","MODAL","CLOVE","SHEIK","MISSY","GRUNT","SNOOP","MAFIA","EMCEE","SKULK","ANGST","TUBAL","CYBER","SWAMI","ROACH","HITCH","PUREE","DRYLY","DRANK","THETA","JUNTO","PIXIE","QUOTH","SHALT","PENNE","SUING","REARM","RAMEN","OVOID","RANDY","ETHER","IDLER","SWATH","BEGAT","SLANG","TAROT","TIMER","BYLAW","SERUM","ILIAC","PUPPY","JOIST","BUNNY","STOLE","TOPAZ","AFOOT","BLOAT","BOXER","JUMBO","LUNGE","CONCH","PLUNK","AFOUL","SAVOY","AROSE","BAGGY","MAMMY","RUGBY","WAGER","SNAKY","DEBIT","MANGE","JOUST","MICRO","COMFY","ESTER","OAKEN","AGLOW","RACER","POESY","TWEET","PATSY","SWISH","GIVER","BEVEL","LEMUR","LINGO","CURLY","CEDAR","GROWN","HORDE","CUMIN","GRAVY","WIDER","BLIMP","ELIDE","RENAL","PENCE","MUCKY","FIERY","SCALP","BITTY","CIDER","KOALA","DUVET","SEGUE","CREME","EMBER","NOBLY","GIPSY","SMOCK","KAPPA","SNORT","SYNOD","DETOX","SHREW","PLIED","QUARK","BURLY","WAXEN","JERKY","BLITZ","BEEFY","HUSSY","BINGO","SCONE","RADII","LAGER","GASSY","WIGHT","RETRY","HOLLY","DECAL","MOVER","CRIER","FLUTE","HIPPO","DRIER","BUGLE","TAWNY","BLEAT","SLUSH","SEMEN","IGLOO","NERDY","SHONE","HYMEN","FUGUE","PANSY","SUAVE","SWUNG","DRAKE","FREED","AFIRE","GROUT","ODDLY","TITHE","PLAID","TYING","GAZER","DECOR","PIPER","SCALY","HEFTY","CHICK","SOOTY","WHINY","SWEPT","FEMME","SPREE","SPENT","PRISM","RIPER","COCOA","HUMUS","SHUSH","ELATE","PIXEL","CLEAT","MANGO","RUMBA","PUFFY","BILLY","OVATE","CHILI","CURIO","NAVEL","PESTO","UNWED","CHURN","WEEDY","WIMPY","SANER","SALSA","WARTY","MANIC","SQUIB","CREPE","TEETH","ICILY","INGOT","ABODE","SLEPT","MORPH","CACTI","TACKY","DEMON","LUMPY","ADMIN","OMEGA","TABBY","MACHO","LOUSY","SLAIN","WRACK","FOIST","HARPY","OLDEN","CAIRN","TULLE","GHOUL","OLDER","SPERM","BRINY","ABBOT","RERUN","BEFIT","ITCHY","BAGEL","CHARD","CAPUT","LEANT","LUPUS","GUMBO","ABLED","BOSSY","MAKER","JAZZY","BEGAN","TORUS","NINNY","COVEY","VIPER","TAKEN","OWING","ELFIN","EBONY","NEIGH","MINIM","KNEED","VOILA","MUSHY","ODDER","TERRA","TRIED","CLACK","UNCUT","DICEY","TITAN","GYPSY","TAFFY","TEARY","WORDY","LEPER","NOSEY","FOAMY","BUTTE","TRIPE","BICEP","KRILL","HYENA","COBRA","BASIL","SCRUM","BUSED","HEARD","POUTY","THROB","FETAL","MACRO","DODGY","SATYR","RARER","BINGE","NUTTY","LEAPT","MYRRH","SONAR","SLOOP","SMOTE","BALER","WAFER","FRILL","AWASH","REVEL","KNELT","DEBUG","ANODE","PINKY","STAVE","CHOCK","PRAWN","BOOTY","APNEA","MEDIC","SLUNK","WOMEN","MUCUS","TODDY","TRUER","AXIAL","PURER","PAPAL","LEGGY","SKIER","UMBRA","AVIAN","SATIN","TRUMP","REUSE","GAYER","BERET","DEALT","TONGA","ETUDE","CIRCA","INBOX","FIZZY","BELLE","SALVO","SAPPY","TAKER","OVINE","SPIKY","SPASM","MAMBO","CLANK","BORNE","SPOOK","AMBER","CORER","SLYLY","TAINT","KINKY","WOOLY","FLUNG","FRIED","GRIMY","CURVY","CAGEY","DEUCE","GIRLY","SWASH","BONEY","COUPE","WELCH","GEESE","LEECH","DROIT","GRAIL","RALPH","BIDDY","SMITH","MOWER","PAYEE","SERIF","DRAPE","KAYAK","TEPEE","FULLY","ZONAL","CURRY","BANJO","AXION","BEZEL","GOOEY","FILER","PUBIC","RAVEN","GNASH","FLAKY","DULLY","EKING","SHORN","MAMMA","FROZE","NEWER","MOOSE","VEGAN","GUPPY","CABBY","DRUID","DOPEY","CRIED","CHIME","CRONY","STUNK","ROTOR","LATTE","SOAPY","BROTH","MADLY","DRIED","KNOWN","ROOST","THONG","PASTY","DOWNY","CLANG","GOOFY","BLEEP","MECCA","FOLIO","SETUP","VERSO","GUMMY","GUAVA","RATTY","FUDGE","FEMUR","GOLLY","THRUM","FICUS","WISER","JUNTA","VISOR","SCREE","TURBO","EYING","INLET","CRONE","MOSSY","TEDDY","SNORE","GOING","TONAL","HAUTE","SPIED","UNDID","INTRO","BASAL","GECKO","LOAMY","SCRAM","VAUNT","CONDO","WILLY","POLKA","SLEEK","RISER","TWIXT","CATTY","LOGIN","ROGER","UNTIE","REFIT","ROWER","ARTSY","APACE","CHINK","FUMES","RESIT"] \ No newline at end of file diff --git a/assets/game/wordle/cet-4.json b/assets/game/wordle/cet-4.json deleted file mode 100644 index 8628a282a..000000000 --- a/assets/game/wordle/cet-4.json +++ /dev/null @@ -1 +0,0 @@ -["ABOUT","ABOVE","ABUSE","ACTOR","ACUTE","ADAPT","ADMIT","ADOPT","ADULT","AFTER","AGAIN","AGENT","AGONY","AGREE","AHEAD","ALARM","ALIKE","ALIVE","ALLOW","ALLOY","ALONE","ALONG","ALOUD","ALTER","AMAZE","AMONG","AMUSE","ANGEL","ANGER","ANGLE","ANGRY","ANKLE","ANNOY","APART","APPLE","APPLY","APRIL","ARGUE","ARISE","ARROW","ASIAN","ASIDE","AURAL","AVOID","AWAIT","AWAKE","AWARD","AWARE","AWFUL","BADLY","BASIC","BASIN","BASIS","BATHE","BEACH","BEARD","BEAST","BEGIN","BEING","BELOW","BENCH","BERRY","BIBLE","BIRTH","BLACK","BLADE","BLAME","BLANK","BLAST","BLAZE","BLEED","BLEND","BLESS","BLIND","BLOCK","BLOOD","BLOOM","BOARD","BOAST","BOOTH","BOSOM","BOUGH","BOUND","BRAIN","BRAKE","BRAND","BRASS","BRAVE","BREAD","BREAK","BREED","BRICK","BRIEF","BRING","BRISK","BROAD","BROOD","BROOK","BROOM","BROWN","BRUSH","BRUTE","BUILD","BUNCH","BURST","CABIN","CABLE","CAMEL","CANAL","CANDY","CANOE","CARGO","CARRY","CARVE","CATCH","CAUSE","CEASE","CHAIN","CHAIR","CHALK","CHART","CHASE","CHEAP","CHEAT","CHECK","CHEEK","CHEER","CHESS","CHEST","CHIEF","CHILD","CHILL","CHINA","CHOKE","CIVIL","CLAIM","CLASP","CLASS","CLEAN","CLEAR","CLERK","CLIFF","CLIMB","CLOAK","CLOCK","CLOSE","CLOTH","CLOUD","COACH","COAST","COLOR","COUGH","COULD","COUNT","COURT","COVER","CRACK","CRAFT","CRANE","CRASH","CRAWL","CRAZY","CREAM","CREEP","CRIME","CROSS","CROWD","CROWN","CRUDE","CRUEL","CRUSH","CRUST","CUBIC","CURSE","CURVE","CYCLE","DAILY","DAIRY","DANCE","DEATH","DECAY","DELAY","DENSE","DEPTH","DEVIL","DIARY","DIRTY","DITCH","DOUBT","DOZEN","DRAFT","DRAIN","DRAMA","DREAD","DREAM","DRESS","DRIFT","DRILL","DRINK","DRIVE","DROWN","DRUNK","DYING","EAGER","EAGLE","EARLY","EARTH","EIGHT","ELBOW","ELDER","ELECT","EMPTY","ENEMY","ENJOY","ENTER","ENTRY","EQUAL","EQUIP","ERECT","ERROR","ESSAY","EVENT","EVERY","EXACT","EXERT","EXIST","EXTRA","FABLE","FAINT","FAITH","FALSE","FANCY","FATAL","FAULT","FEAST","FENCE","FETCH","FEVER","FIBRE","FIELD","FIFTH","FIFTY","FIGHT","FINAL","FIRST","FLAME","FLARE","FLASH","FLEET","FLESH","FLOAT","FLOCK","FLOOD","FLOOR","FLOUR","FLUID","FLUSH","FOCUS","FORCE","FORTH","FORTY","FOUND","FRAME","FRANK","FRESH","FRONT","FROST","FROWN","FRUIT","FUNNY","GAUGE","GHOST","GIANT","GLARE","GLASS","GLIDE","GLOBE","GLORY","GLOVE","GOODS","GOOSE","GRACE","GRADE","GRAIN","GRAND","GRANT","GRAPE","GRAPH","GRASP","GRASS","GRAVE","GREAT","GREEK","GREEN","GREET","GRIND","GROAN","GROSS","GROUP","GUARD","GUESS","GUEST","GUIDE","HABIT","HANDY","HAPPY","HARSH","HASTE","HASTY","HATCH","HEART","HEAVY","HEDGE","HELLO","HENCE","HOBBY","HONEY","HORSE","HOTEL","HOUSE","HUMAN","HUMID","HURRY","IDEAL","IDIOM","IMAGE","IMPLY","INDEX","INDIA","INFER","INNER","INPUT","ISSUE","JAPAN","JEWEL","JOINT","JOLLY","JUDGE","JUICE","KNEEL","KNIFE","KNOCK","LABEL","LARGE","LASER","LATER","LATIN","LAUGH","LAYER","LEARN","LEAST","LEAVE","LEGAL","LEMON","LEVEL","LEVER","LIGHT","LIGHT","LIMIT","LINEN","LINER","LITER","LIVER","LOCAL","LODGE","LOGIC","LOOSE","LORRY","LOVER","LOWER","LOYAL","LUCKY","LUNCH","MADAM","MAGIC","MAJOR","MANLY","MARCH","MARRY","MATCH","MATHS","MAYBE","MAYOR","MEANS","MEDAL","MELON","MERCY","MERIT","MERRY","METAL","METER","METRE","MIDST","MIGHT","MIGHT","MINER","MINOR","MINUS","MODEL","MOIST","MONEY","MONTH","MORAL","MOTOR","MOULD","MOUNT","MOURN","MOUSE","MOUTH","MOVIE","MUDDY","MUSIC","NAKED","NASTY","NAVAL","NEGRO","NERVE","NEVER","NEWLY","NIECE","NIGHT","NINTH","NOBLE","NOISE","NOISY","NORTH","NOVEL","NURSE","NYLON","OCCUR","OCEAN","ODOUR","OFFER","OFTEN","ONION","OPERA","ORBIT","ORDER","ORGAN","OTHER","OUGHT","OUNCE","OUTER","OWNER","PAINT","PANDA","PANEL","PAPER","PARTY","PASTE","PATCH","PAUSE","PEACE","PEACH","PEARL","PENNY","PHASE","PHONE","PIANO","PIECE","PILOT","PINCH","PITCH","PITCH","PLACE","PLAIN","PLANE","PLANT","PLATE","PLUCK","POINT","PORCH","POUND","POWER","PRESS","PRICE","PRIDE","PRIME","PRINT","PRIOR","PRIZE","PROOF","PROUD","PROVE","PULSE","PUNCH","PUPIL","PURSE","QUART","QUEEN","QUEER","QUEUE","QUICK","QUIET","QUILT","QUITE","QUOTE","RADAR","RADIO","RAINY","RAISE","RANGE","RAPID","RATIO","RAZOR","REACH","REACT","READY","REALM","REBEL","REFER","REIGN","RELAX","RENEW","REPLY","RIDER","RIDGE","RIFLE","RIGHT","RIGID","RIPEN","RIVAL","RIVER","ROAST","ROBOT","ROMAN","ROUGH","ROUND","ROUSE","ROUTE","ROYAL","RULER","RURAL","RUSTY","SADLY","SAINT","SALAD","SANDY","SAUCE","SCALE","SCARE","SCARF","SCENE","SCENT","SCOLD","SCOPE","SCORE","SCORN","SCOUT","SCREW","SEIZE","SENSE","SERVE","SEVEN","SHADE","SHADY","SHAKE","SHALL","SHAME","SHAPE","SHARE","SHARP","SHAVE","SHEAR","SHEEP","SHEET","SHELF","SHELL","SHIFT","SHINE","SHIRT","SHOCK","SHOOT","SHORE","SHORT","SHOUT","SIGHT","SILLY","SINCE","SIXTH","SIXTY","SKATE","SKILL","SKIRT","SLAVE","SLEEP","SLICE","SLIDE","SLOPE","SMALL","SMART","SMELL","SMILE","SMOKE","SNAKE","SNOWY","SOBER","SOLAR","SOLID","SOLVE","SORRY","SOUND","SOUTH","SPACE","SPADE","SPARE","SPARK","SPEAK","SPEAR","SPEED","SPELL","SPEND","SPILL","SPLIT","SPOIL","SPOON","SPORT","SPRAY","STACK","STAFF","STAGE","STAIN","STAIR","STAKE","STALE","STAMP","STAND","STARE","START","STATE","STEAL","STEAM","STEEL","STEEP","STEER","STERN","STICK","STIFF","STILL","STING","STOCK","STONE","STONY","STOOL","STOOP","STORE","STORM","STORY","STOVE","STRAP","STRAW","STRIP","STUDY","STUFF","STYLE","SUGAR","SUNNY","SUPER","SWAMP","SWARM","SWEAR","SWEAT","SWEEP","SWEET","SWELL","SWIFT","SWING","SWISS","SWORD","TABLE","TASTE","TEACH","TEMPT","TENSE","TENTH","THANK","THEIR","THERE","THESE","THICK","THIEF","THING","THINK","THIRD","THORN","THOSE","THREE","THROW","THUMB","TIGER","TIGHT","TIMID","TIRED","TITLE","TOAST","TODAY","TOOTH","TOPIC","TORCH","TOTAL","TOUCH","TOUGH","TOWEL","TOWER","TRACE","TRACK","TRADE","TRAIL","TRAIN","TRAMP","TREAT","TREND","TRIAL","TRIBE","TRICK","TROOP","TRUCK","TRULY","TRUNK","TRUST","TRUTH","TUTOR","TWICE","TWIST","UNCLE","UNDER","UNION","UNITE","UNITY","UNTIL","UPPER","UPSET","USAGE","USUAL","UTTER","VAGUE","VALID","VALUE","VIDEO","VISIT","VITAL","VIVID","VOICE","WAIST","WAKEN","WASTE","WATCH","WATER","WEARY","WEAVE","WEIGH","WHEAT","WHEEL","WHERE","WHICH","WHILE","WHIRL","WHITE","WHOLE","WHOSE","WIDEN","WIDOW","WIDTH","WOMAN","WORLD","WORRY","WORSE","WORST","WORTH","WOULD","WOUND","WRECK","WRIST","WRITE","WRONG","YIELD","YOUNG","YOURS","YOUTH","ZEBRA"] \ No newline at end of file diff --git a/assets/game/wordle/cet-6.json b/assets/game/wordle/cet-6.json deleted file mode 100644 index 7aec73376..000000000 --- a/assets/game/wordle/cet-6.json +++ /dev/null @@ -1 +0,0 @@ -["ABIDE","ABORT","ABOUT","ABOVE","ABUSE","ACTOR","ACUTE","ADAPT","ADMIT","ADOPT","ADORE","ADULT","AFFIX","AFTER","AGAIN","AGING","AGENT","AGONY","AGREE","AHEAD","AISLE","ALARM","ALBUM","ALERT","ALIEN","ALIGN","ALIKE","ALIVE","ALLEY","ALLOT","ALLOW","ALLOY","ALOFT","ALONE","ALONG","ALOUD","ALTER","AMASS","AMAZE","AMEND","AMONG","AMPLE","AMUSE","ANGEL","ANGLE","ANGRY","ANGER","ANKLE","ANNEX","ANNOY","APART","APPAL","APPLE","APPLY","APRIL","APRON","APTLY","ARENA","ARGUE","ARISE","ARMOR","ARRAY","ARROW","ASIDE","ASSET","ATLAS","ATTIC","AUDIO","AUDIT","AVAIL","AVERT","AVOID","AWAIT","AWAKE","AWARD","AWARE","AWFUL","BACON","BADGE","BADLY","BARGE","BASIC","BASIN","BASIS","BATCH","BATHE","BEACH","BEARD","BEAST","BEGIN","BEING","BELLY","BELOW","BENCH","BERRY","BESET","BIBLE","BIRTH","BITCH","BLACK","BLADE","BLAME","BLANK","BLAST","BLAZE","BLEAK","BLEED","BLEND","BLESS","BLIND","BLINK","BLOCK","BLOND","BLOOD","BLOOM","BLUFF","BLUNT","BLUSH","BOARD","BOAST","BONUS","BOOST","BOOTH","BOUND","BOWEL","BRACE","BRAIN","BRAKE","BRAND","BRASS","BRAVE","BREAD","BREAK","BREED","BRIBE","BRICK","BRIDE","BRIEF","BRING","BRINK","BRISK","BROAD","BROKE","BROOD","BROOM","BROWN","BRUSH","BUDDY","BUILD","BULKY","BULLY","BUNCH","BURST","BUSHY","BUYER","CABIN","CABLE","CAMEL","CANAL","CANDY","CANOE","CARGO","CARRY","CARVE","CATCH","CATER","CAUSE","CEASE","CHAIN","CHAIR","CHALK","CHANT","CHAOS","CHARM","CHART","CHASE","CHEAP","CHEAT","CHECK","CHEEK","CHEER","CHESS","CHEST","CHIEF","CHILD","CHILL","CHINA","CHOIR","CHOKE","CHORD","CHORE","CHUNK","CIGAR","CIVIC","CIVIL","CLAIM","CLAMP","CLASH","CLASP","CLASS","CLEAN","CLEAR","CLERK","CLICK","CLIFF","CLIMB","CLING","CLOAK","CLOCK","CLONE","CLOSE","CLOTH","CLOUD","CLOWN","COACH","COAST","COLON","COLOR","COMET","COMIC","COMMA","CORAL","CORPS","COUCH","COUGH","COULD","COUNT","COURT","COVER","CRACK","CRAFT","CRANE","CRASH","CRATE","CRAVE","CRAWL","CRAZY","CRAZE","CREAM","CREEP","CRIME","CRISP","CROSS","CROWD","CROWN","CRUDE","CRUEL","CRUSH","CRUST","CUBIC","CURSE","CURVE","CYCLE","DADDY","DAILY","DAIRY","DANCE","DATUM","DAILY","DEATH","DEBUT","DECAY","DEPTH","DEFER","DELAY","DENSE","DEPOT","DEPTH","DETER","DEVIL","DIARY","DYING","DIGIT","DINER","DIRTY","DISCO","DITCH","DIZZY","DODGE","DOGMA","DONOR","DOUBT","DOUGH","DOZEN","DRAFT","DRAIN","DRAMA","DREAD","DREAM","DRESS","DRIFT","DRILL","DRINK","DRIVE","DROWN","DRUNK","DRYER","DWARF","DUSTY","DWELL","EAGER","EAGLE","EARLY","EARTH","EIGHT","EJECT","ELBOW","ELDER","ELECT","ELITE","EMAIL","EMBED","EMPTY","ENACT","ENDOW","ENEMY","ENJOY","ENROL","ENSUE","ENTER","ENTRY","ENVOY","EPOCH","EQUAL","EQUIP","ERASE","ERECT","ERODE","ERROR","ERUPT","ESSAY","ETHIC","EVADE","EVENT","EVERY","EVOKE","EXACT","EXCEL","EXERT","EXILE","EXIST","EXPEL","EXTRA","FABLE","FACET","FAINT","FAIRY","FAITH","FALSE","FANCY","FATAL","FAULT","FAVOR","FEAST","FENCE","FERRY","FETCH","FEVER","FIBER","FIBRE","FIELD","FIFTH","FIFTY","FIGHT","FINAL","FIRST","FIXED","FLAME","FLANK","FLARE","FLASH","FLEET","FLESH","FLING","FLIRT","FLOAT","FLOCK","FLOOD","FLOOR","FLOUR","FLUID","FLUSH","FOCUS","FOGGY","FORCE","FORGE","FORTH","FORTY","FORUM","FOUND","FRAME","FRANK","FRAUD","FRESH","FRONT","FROST","FROWN","FRUIT","FUNNY","GAUGE","GENRE","GHOST","GIANT","GIVEN","GLAND","GLARE","GLASS","GLEAM","GLIDE","GLOBE","GLOOM","GLORY","GLOVE","GOODS","GOOSE","GORGE","GRACE","GRADE","GRAIN","GRAND","GRANT","GRAPE","GRAPH","GRASP","GRASS","GRAVE","GRAZE","GREAT","GREED","GREEN","GREET","GRIEF","GRILL","GRIND","GROAN","GROPE","GROSS","GROUP","GUARD","GUESS","GUEST","GUIDE","GUILT","HABIT","HAIRY","HANDY","HAPPY","HARDY","HARSH","HASTE","HASTY","HATCH","HAUNT","HAVEN","HAVOC","HEART","HEAVE","HEAVY","HEDGE","HELLO","HENCE","HILLY","HINGE","HOBBY","HOIST","HONEY","HONOR","HORSE","HOTEL","HOUND","HOUSE","HOVER","HUMAN","HUMID","HUMOR","HURRY","IDEAL","IDIOM","IDIOT","IMAGE","IMPLY","INCUR","INDEX","INFER","INNER","INPUT","IRONY","ISSUE","IVORY","JEANS","JELLY","JEWEL","JOINT","JOLLY","JUDGE","JUICE","JUICY","JUROR","KNEEL","KNIFE","KNOCK","LABEL","LABOR","LARGE","LASER","LAUGH","LATER","LAYER","LEARN","LEASE","LEAST","LEAVE","LEGAL","LEMON","LEVEL","LEVER","LIGHT","LIMIT","LINEN","LINER","LITER","LITRE","LIVER","LOBBY","LOCAL","LODGE","LOFTY","LOGIC","LOOSE","LORRY","LOVER","LOWER","LOYAL","LUCKY","LUNAR","LUNCH","LYRIC","MADAM","MAGIC","MAJOR","MANLY","MARCH","MARCH","MARRY","MARSH","MATCH","MATHS","MAYBE","MAYOR","MEANS","MEATY","MEDAL","MEDIA","MELON","MERCY","MERGE","MERIT","MERRY","MESSY","METAL","METER","METRE","METRO","MIDST","MIGHT","MILKY","MINOR","MINUS","MISTY","MIXER","MODEL","MOIST","MOULD","MOLDY","MOMMY","MONEY","MONTH","MOODY","MORAL","MOTEL","MOTOR","MOUNT","MOURN","MOUSE","MOUTH","MOVIE","MUDDY","MUSIC","NAIVE","NAKED","NASTY","NAVAL","NEEDY","NERVE","NEVER","NIECE","NIGHT","NINTH","NOBLE","NOISE","NOISY","NORTH","NOVEL","NURSE","OBESE","OCCUR","OCEAN","ODOUR","OFFER","OFTEN","OLIVE","ONION","ONSET","OPERA","OPTIC","ORBIT","ORDER","ORGAN","OTHER","OUNCE","OUTER","OVERT","OWNER","OXIDE","OZONE","PADDY","PAINT","PANDA","PANEL","PANIC","PANTS","PAPER","PARTY","PASTA","PASTE","PATCH","PAUSE","PEACE","PEACH","PEARL","PEDAL","PENNY","PERCH","PERIL","PETTY","PHASE","PHOTO","PIANO","PICKY","PIECE","PILOT","PINCH","PITCH","PIZZA","PLACE","PLAIN","PLANE","PLANT","PLATE","PLAZA","PLEAD","POINT","POISE","POLAR","PORCH","POUND","POWER","PRESS","PRICE","PRIDE","PROUD","PRIME","PRINT","PRIOR","PRIZE","PROBE","PRONE","PROOF","PROSE","PROVE","PULSE","PUNCH","PUPIL","PURSE","PUSHY","QUAKE","QUART","QUEEN","QUERY","QUEST","QUEUE","QUICK","QUIET","QUITE","QUOTA","QUOTE","RADAR","RADIO","RAINY","RAISE","RALLY","RANCH","RANGE","RAPID","RATIO","REACH","REACT","READY","REALM","REBEL","RECUR","REFER","REIGN","RELAX","RELAY","RELIC","RENEW","REPAY","REPEL","REPLY","RIDER","RIDGE","RIFLE","RIGHT","RIGID","RIGOR","RIPEN","RISKY","RIVAL","RIVER","ROAST","ROBOT","ROCKY","ROUGH","ROUND","ROUSE","ROUTE","ROYAL","RUMOR","RULER","RURAL","RUSTY","SAINT","SALAD","SALON","SALTY","SAUCE","SCALE","SCARE","SCARY","SCENE","SCENT","SCOLD","SCOPE","SCORE","SCORN","SCOUT","SCRAP","SCREW","SCRUB","SEIZE","SENSE","SERVE","SEVEN","SHADE","SHADY","SHAFT","SHAKE","SHAKY","SHALL","SHAME","SHAPE","SHARE","SHARK","SHARP","SHAVE","SHEEP","SHEER","SHEET","SHELF","SHELL","SHIFT","SHINE","SHINY","SHIRT","SHOCK","SHOOT","SHORE","SHORT","SHOUT","SHOVE","SHOWY","SHRED","SHRUB","SHRUG","SHYLY","SIEGE","SIGHT","SILKY","SINCE","SIXTY","SKATE","SKILL","SKIRT","SKULL","SLACK","SLASH","SLAVE","SLEEP","SLICE","SLIDE","SLOPE","SLUMP","SMALL","SMART","SMASH","SMELL","SMILE","SMOKE","SMOKY","SNACK","SNAIL","SNAKE","SNEAK","SNIFF","SNOWY","SOBER","SOBRE","SOLAR","SOLID","SOLVE","SORRY","SOUND","SOUTH","SPACE","SPADE","SPARE","SPARK","SPEAK","SPEAR","SPEED","SPELL","SPEND","SPICE","SPICY","SPILL","SPINE","SPITE","SPLIT","SPOIL","SPOON","SPORT","SPRAY","SQUAD","STACK","STAFF","STAGE","STAIN","STAIR","STAKE","STALE","STALK","STALL","STAMP","STAND","STARE","STARK","START","STATE","STEAK","STEAL","STEAM","STEEL","STEEP","STEER","STERN","STICK","STIFF","STILL","STING","STOCK","STONE","STONY","STOOL","STOOP","STORE","STORM","STORY","STOUT","STOVE","STRAP","STRAW","STRAY","STRIP","STUDY","STUFF","STUMP","STYLE","SUGAR","SUITE","SUNNY","SUPER","SURGE","SWAMP","SWEAR","SWEAT","SWEEP","SWEET","SWELL","SWIFT","SWING","SWIPE","SWORD","TABLE","TABOO","TASTE","TASTY","TEACH","TEASE","PHONE","TEMPO","TEMPT","TENSE","THANK","THEFT","THEIR","THEME","THERE","THESE","THICK","THIEF","THIGH","THING","THINK","THIRD","THORN","THOSE","THREE","THROW","THUMB","TIDAL","TIGER","TIGHT","TIMID","TIRED","TITLE","TOAST","TODAY","TOKEN","TOOTH","TOPIC","TORCH","TOTAL","TOUCH","TOUGH","TOWEL","TOWER","TOXIC","TRACE","TRACK","TRACT","TRADE","TRAIL","TRAIN","TRAIT","TRASH","TREAD","TREAT","TREND","TRIAL","TRIBE","TRICK","TROOP","TRUCK","TRULY","TRUNK","TRUST","TRUTH","TRIAL","TUMOR","TUTOR","TWICE","TWIST","ULTRA","UNCLE","UNDER","UNIFY","UNION","UNITE","UNITY","UNTIL","UPPER","UPSET","URBAN","URINE","USAGE","USUAL","UTTER","VAGUE","VALID","VALUE","VALVE","VAPOR","VENUE","VERGE","VERSE","VIDEO","VIGOR","VIRUS","VISIT","VITAL","VIVID","VOCAL","VOGUE","VOICE","VOTER","WAGON","WAIST","WAIVE","WAKEN","WASTE","WATCH","WATER","WEARY","WEAVE","WEDGE","WEIGH","WEIRD","WHALE","WHARF","WHEAT","WHEEL","WHERE","WHICH","WHILE","WHIRL","WHITE","WHOLE","WHOSE","WIDTH","WIDEN","WIDOW","WIELD","WINDY","WITTY","WITCH","WOMAN","WORLD","WORRY","WORSE","WORST","WORTH","WOULD","WOUND","WRECK","WRIST","WRITE","WRONG","YACHT","YEARN","YIELD","YOUNG","YOURS","YOUTH"] \ No newline at end of file diff --git a/assets/game/wordle/ielts.json b/assets/game/wordle/ielts.json deleted file mode 100644 index 6cdf20092..000000000 --- a/assets/game/wordle/ielts.json +++ /dev/null @@ -1 +0,0 @@ -["PLUSH","ERODE","CHECK","SPOIL","PILOT","BLOCK","TIMID","ENSUE","COACH","IRONY","SCRAP","ROAST","AVAIL","CHEAT","FLORA","CRAWL","CORAL","HATCH","PROBE","MERGE","THIGH","SMELL","MANOR","CHEST","STARK","STARE","SHORE","EQUAL","STASH","INCUR","GLAND","INLET","SOLAR","BLOOM","CORPS","STACK","CRACK","CRISP","SMEAR","GRASP","RIGID","STAFF","STAGE","LEGAL","MIDST","STRAP","STRAW","GRAPH","FERRY","LUNAR","RELAX","RELAY","ABODE","GRAND","GRANT","STALE","PITCH","STALL","CURRY","UPSET","STAMP","CRIME","BONUS","AISLE","STRIP","ALTER","IDEAL","BROOM","BROOD","BLEND","STAIN","STAKE","TAUNT","OUTDO","CRASH","ORBIT","BOOTH","BOOST","EXPEL","STIFF","SWAMP","GLIDE","CRANK","DENSE","ADMIT","APPLY","TREND","DIZZY","GORGE","CHURN","PUNCH","STINT","HASTY","CRAFT","ALLEY","CHUNK","LIBEL","HUMID","SLASH","AWFUL","HANDY","SLEEK","LEASE","CRUEL","SHADE","LOOSE","FLINT","LINEN","FORUM","SWEAR","SHAFT","MORAL","FORTH","SWELL","CRUDE","VALID","SHARK","LOGIC","SPICE","NAKED","TRIAL","MOIST","CABLE","NASTY","BRASS","TRICK","SHIFT","PEARL","AMAZE","FLASK","FLASH","DWELL","BOAST","CATER","URBAN","REFER","FLAME","CRUSH","EMBED","LIVER","METRO","ABATE","AMASS","CRUST","BOARD","STUFF","WEIGH","ACUTE","FAITH","MAGMA","CARVE","PANIC","FRANK","VIRUS","ADOBE","NAIVE","APACE","SCARE","MURKY","IMAGE","SCRUB","CARRY","BURST","FRAME","VENUE","CLASH","HOVER","ALERT","TEASE","RENEW","CLAMP","ISSUE","LOWER","SLUMP","HALVE","CLAIM","TOXIC","ESSAY","TOXIN","BRIEF","EQUIP","BRAND","ADOPT","APART","JOINT","FORGO","SOLVE","SNACK","SCREW","ARGUE","DAUNT","PROOF","EXIST","EXACT","FORCE","ABIDE","ASSET","MAGIC","FRAUD","EXILE","SCALE","OUNCE","GROPE","UNION","CHILL","CHINK","FATAL","FOCUS","CAMEL","GLEAM","STOCK","SURGE","BASIS","TUTOR","TEMPT","TWIST","EXTRA","SKULL","CHIEF","GROAN","DECAY","MARSH","CHAOS","INPUT","CHASE","OXIDE","CHART","EVOKE","GIVEN","ADAPT","BATCH","OZONE","ODOUR","PEDAL","SLOPE","BRUNT","SMART","LAYER","ELITE","CHAIN","GROVE","MAJOR","GROSS","FIBRE","WAGON","POINT","TRAMP","PRIME","ROUTE","AGILE","ORGAN","EDIFY","SPARK","COMET","BULLY","SPARE","BLAST","REPEL","SPASM","BULGE","PRIOR","BARON","BLAME","HINGE","BLANK","SINEW","GAUGE","BLADE","BARGE","OCCUR","INFER","ANNOY","COVER","SPRAY","VOMIT","EXTOL","RATIO","SHRUG","SHRUB","SPILL","VITAL","SHAVE","SPINE","STEEP","BLAZE","STEER","EVENT","FUMES","SPITE","SPADE","TRACE","ARRAY","STEAM","TRACK","REPAY","COMIC","VAGUE","SCORE","QUOTE","QUOTA","SWING","SCOUT","SCOUR","FLEET","RURAL","SWIFT","SHELL","LASER","SCOPE","LABEL","EJECT","FROCK","TOUGH","YIELD","GLOVE","GLOSS","BLUNT","MOUNT","CURLY","ENACT","TRUCE","HENCE","FROWN","GRIMY","GRIND","FANCY","SLICE","LARVA","TRUNK","SLIDE","WAIST","GRILL","HARSH","BLEAK","DAIRY","NERVE","RESIT","SHEER","CANOE","WEDGE","DRAFT","LOATH","DELAY","ABUSE","CIVIL","BOUND","BREED","REALM","AVOID","MODEM","CROWN","ANGLE","OPTIC","SPLIT","HELIX","KNEEL","LEVEL","LEVER","DETER","BUNCH","TENSE","DRILL","CLERK","LOBBY","MOULD","ELBOW","UPPER","DELTA","SCENT","BADGE","AUDIT","AUDIO","VIVID","FLUSH","RANGE","CEASE","FAUNA","STOUT","AWARE","AWARD","STOVE","ALARM","MATCH","FAULT","RIVAL","POWER","GROUP","PRICE","COURT","LIMIT","OFFER","BRING","SHARE","CLASS","SLAVE","BRAIN","ADULT","HAPPY","SENSE","SPEND","PLANT","SPEAK","AGREE","ORDER","SLEEP","CATCH","BASIC","DRINK","VISIT","MODEL","TRADE","STAND","USUAL","GUIDE","SMOKE","WRONG","PROVE","STORE","STORY","DOUBT","UNITE","FAVOR","LABOR","SIGHT","TRUST","WORTH","ENTER","MONTH","DAILY","STICK","SHORT","DRIVE","FIGHT","READY","JUDGE","GRAPE","HOTEL","TRUTH","BIRTH","TOPIC","PIECE","GOODS","SPORT","THEME","NORTH","ROUND","TOKEN","PRINT","IMPLY","ACTOR","WORST","SMILE","EXERT","SOUTH","SOUND","SHAPE","ALIKE","ROYAL","SCENE","SPACE","CHEER","VOICE","TRAIT","RAPID","EVADE","ARISE","YOUTH","STYLE","QUEST","PANEL","LAUGH","FRUIT","QUICK","LOCAL","TOTAL","TASTE","COUNT","MARRY","REPLY","BROWN","BROOK","CHEAP","OWNER","PAINT","PLATE","CLICK","NOBLE","SLACK","REACT","GUESS","BEARD","CHINA","PRIZE","CLOCK","PRIDE","PUPIL","RADIO","AHEAD","TREAT","SUPER","NOISE","BIBLE","WORSE","LUCKY","FINAL","PROUD","QUIET","SPELL","FRESH","MARCH","WATCH","MOUTH","PLEAD","CHAIR","HEAVY","HONOR","INDEX","UNITY","SOBER","TABLE","RECUR","RALLY","CLEAN","THUMB","PURSE","GLARE","CANAL","FLESH","STEAL","BLOOD","THEFT","TOWER","RIVER","CLIFF","CLING","CLIMB","ALIVE","DRESS","PENNY","CLOAK","LODGE","SOLID","AMUSE","GRAVE","ANGER","GRAIN","CLOUD","OUTER","PANTS","FLOAT","PHOTO","DOZEN","ROBOT","PHONE","SUITE","FLOOR","TIRED","AWAKE","CROWD","ELECT","JEANS","MERIT","FALSE","COAST","FORGE","PRONE","PROSE","CURVE","OPERA","PHASE","SPEED","SHELF","FAINT","SEIZE","WASTE","APRIL","SHOOT","LOYAL","DEPTH","TOAST","WEIRD","GLOBE","SHARP","SHOCK","HEDGE","ERUPT","BASIN","GUILT","VERGE","WEARY","PEACH","PEACE","WHIRL","HASTE","WEAVE","ALIEN","WHALE","CHESS","SUGAR","FLUID","PINCH","BOWEL","RUMOR","MEDAL","MERCY","REIGN","GIANT","RIDGE","BATHE","HOIST","SPICY","CHEEK","DWARF","GLASS","NOISY","SKIRT","BRISK","MERRY","SKATE","SCORN","POLAR","STERN","ERECT","SCOLD","SALAD","ALBUM","BRIBE","BRICK","ELDER","BRIDE","SAUCE","TIGHT","BLIND","SWORD","QUART","TIGER","STEAK","ANKLE","GOOSE","EAGLE","CLASP","BRAKE","STEEL","RULER","ENTRY","BACON","RAZOR","DROWN","UTTER","PULSE","ONION","SNEAK","SIEGE","SPOON","CYCLE","BLESS","CRANE","THORN","GRIEF","METAL","REBEL","COUCH","QUILT","CANDY","FIFTY","HOBBY","LEMON","JEWEL","CURSE","HURRY","ERASE","TOWEL","RIFLE","GHOST","STORM","PASTE","SHEEP","WRECK","STOOL","STOOP","VOWEL","STONE","DRAIN","TRASH","METRE","AMEND","IDIOM","ROUSE","FEAST","IDIOT","MELON","PORCH","PLANE","TRAIL","LOVER","DRIFT","AGONY","CRAZY","AMPLE","SMASH","DEVIL","SHEAR","BENCH","GRACE","GRADE","VOCAL","FROST","VERSE","PLAIN","SPEAR","MAYOR","BOSOM","TRIBE","EXCEL","GUEST","WITCH","LORRY","APPAL","HONEY","MOVIE","CIGAR","PATCH","FABLE","APPLE","FAIRY","CHALK","SCARF","MOUSE","DIRTY","MOURN","LITRE","BLUSH","BEACH","THIEF","CLONE","GRAZE","STAIR","UNIFY","NEGRO","PAUSE","STING","COUGH","STALK","POUND","THROW","BREAD","KNIFE","NIECE","FLING","NAVAL","HEAVE","SWARM","AVERT","BLEED","ARROW","JUICE","PRICK","WHEEL","BELLY","LAPSE","QUEUE","ANGEL","ANGRY","GRASS","FLARE","CARGO","CLOTH","SAINT","SNAKE","SILLY","ZEBRA","ALLOY","WHEAT","MATHS","LINER","QUEER","DITCH","CREAM","FLOCK","CREEP","FIBER","GREEN","GREET","NYLON","FLOOD","SHOVE","MOTEL","SHOUT","PANDA","SNIFF","SWEAT","DRUNK","ADORE","SWEEP","HOUND","OWING","USAGE","JOLLY","ALOUD","RADAR","NURSE","TORCH","CHOKE","WRIST","PETTY","ENDOW","DREAD","LOFTY","GLORY","WOUND","FEVER","HUMOR","WIDOW","SHADY","WIDTH","FLOUR","SHAKE","TEMPO","SHINE","FENCE","DIARY","SHIRT","AWAIT","ENEMY","EPOCH"] \ No newline at end of file diff --git a/assets/game/wordle/toefl.json b/assets/game/wordle/toefl.json deleted file mode 100644 index 7426fb072..000000000 --- a/assets/game/wordle/toefl.json +++ /dev/null @@ -1 +0,0 @@ -["ABATE","ABHOR","ABUSE","ACRID","ACUTE","ADAGE","ADAPT","ADEPT","ADMIT","ADOPT","ADORE","AGILE","ALERT","ALIEN","ALLOT","ALONE","ALTER","AMASS","AMEND","AMPLE","ANGLE","ANNEX","ANNOY","ANNUL","ARDOR","ARMOR","AROMA","ARRAY","ASSET","AUDIT","AVERT","AVOID","BALMY","BASIN","BASTE","BEGET","BELIE","BESET","BLAME","BLAND","BLAST","BLEAK","BLEED","BLEND","BLINK","BLISS","BLOCK","BLUNT","BOARD","BOAST","BONUS","BOUND","BRACE","BRAIN","BRAWL","BREED","BRINK","BRISK","BUDGE","BULKY","CANNY","CAUSE","CHAFE","CHALK","CHAOS","CHALK","CHAOS","CHARY","CHECK","CHIDE","CHILL","CHIRP","CHOKE","CHORE","CLAMP","CLEAN","CLEFT","CLING","CLOSE","COMET","CORPS","COURT","COVER","COWER","CRAFT","CRASH","CRAVE","CRAZY","CREED","CREEK","CRISP","CRUDE","CRUSH","CRUST","CUBIC","DATED","DECAY","DECRY","DEFER","DEITY","DELAY","DEMUR","DENSE","DEPOT","DINGY","DODGE","DOGMA","DONOR","DOUBT","DREAD","DUSKY","DWELL","EAGER","EDIFY","EJECT","ELITE","ELUDE","EMEND","EMPTY","ENACT","ENVOY","EPOCH","EQUAL","ERASE","ERECT","ERODE","ERUPT","EVADE","EVOKE","EXALT","EXERT","EXILE","EXIST","EXTOL","EXUDE","FABLE","FACET","FAINT","FARCE","FEIGN","FINAL","FINCH","FISHY","FIXED","FLARE","FLORA","FLOUT","FLUSH","FORGE","FORTE","FORTH","FRAIL","FRANK","FRAUD","FROWN","FUSSY","FUZZY","GAUGE","GENRE","GLAZE","GLEAM","GLEAN","GLIDE","GLOOM","GORGE","GRAND","GRASP","GRIND","GROPE","GUISE","HANDY","HARDY","HARRY","HARSH","HASTY","HATCH","HAUNT","HAVEN","HEAVE","HINGE","HOIST","HUMAN","HUMID","IDIOT","IMBUE","IMPEL","INCUR","INEPT","INERT","INFER","INURE","ISSUE","LEASE","LEASE","LEASH","LEDGE","LITHE","LOATH","LOFTY","LOOSE","LUSTY","LYRIC","MAJOR","MANIA","MAXIM","MEANS","MERGE","MERIT","MESSY","MISER","MOIST","MORON","MOTIF","NAVAL","NEEDY","NOTED","OASIS","OFFER","ONSET","OPERA","ORBIT","PANIC","PARTY","PENAL","PERIL","PHASE","PIOUS","PIVOT","PLEAD","PLUMB","POISE","PROBE","PRONE","PROXY","PRUNE","PUNCH","PURGE","QUEER","QUELL","QUERY","QUOTA","RAISE","RALLY","RATIO","REALM","REBEL","REFER","REIGN","RELAX","REMIT","REPEL","RIGID","RIGOR","RIVAL","ROUGH","SAVOR","SCALE","SCARE","SCOOP","SCOPE","SCOUT","SCRAP","SEIZE","SEVER","SHADE","SHAFT","SHAKE","SHEAR","SHEER","SHOVE","SIEGE","SILLY","SINGE","SLANT","SLOPE","SLUMP","SMASH","SMEAR","SMELL","SNEAK","SNEER","SOBER","SOLAR","SPAWN","SPINE","SPOIL","SPOUT","SPRAY","SPURT","SQUAD","STAFF","STALE","STALK","STALL","STAND","STIFF","STILL","STOUT","STRAP","STRIP","STUNT","SWARM","SWELL","SWIFT","TABOO","TACIT","TALLY","TARDY","TASTE","TAUNT","TEASE","TEMPT","TENSE","TEPID","THEFT","THUMP","TOWER","TRACE","TRAIT","TRASH","TREND","TRIAL","UPSET","URBAN","VAGUE","VALID","VALOR","VAULT","VERSE","VIGOR","VITAL","VIVID","VOGUE","WATCH","WAVER","WEIRD","WIELD","WITTY","WRATH","WRING","YEARN","ABORT","ADOBE","ADORN","ALARM","ALLAY","ALLOY","AMINO","AMUSE","AUDIO","AWARE","AWFUL","BARGE","BISON","BLADE","BLOOM","BOOST","BRAND","BRASS","BRIEF","BRUSH","BUDDY","CABIN","CABLE","CARGO","CARVE","CEASE","CHART","CHIEF","CHOIR","CLAIM","CLIFF","CLUMP","COMIC","CORAL","CRACK","CRAWL","CREST","CROWN","CURVE","DAIRY","DEBUT","DELTA","DETER","DRAFT","DRAIN","DRIFT","DRILL","DWARF","ENDOW","ENTRY","ESSAY","EXPEL","FANCY","FATAL","FAULT","FAUNA","FIBER","FLAKE","FLAME","FLASK","FLINT","FLOAT","FLOCK","FRAME","FROST","GIANT","GIVEN","GLAND","GLARE","GOURD","GRACE","GRAIN","GRANT","GRAPH","GROAN","GROOM","GROSS","HASTE","HOVER","IDEAL","IDIOM","IMPLY","INNER","IRONY","IVORY","JEANS","JELLY","JOINT","JUICE","LABEL","LARVA","LASER","LATIN","LAYER","LEVEL","LINEN","LIPID","LOBBY","LODGE","LOWER","LOYAL","LUNAR","MAGMA","MARCH","MARSH","MASON","MATCH","MEDAL","MEDIA","MERCY","MIMIC","MINOR","MODEM","MORAL","MOTOR","MOUNT","MURAL","NAIVE","NAKED","NICHE","NOVEL","OCCUR","OXIDE","PANEL","PANTS","PASTE","PATCH","PILOT","PITCH","PLAIN","PLATE","PLUMP","POLAR","POSIT","POUND","PRESS","PRIME","PRIOR","PRIZE","PROOF","PROSE","PULSE","PUPIL","QUILT","QUOTE","RADAR","RANCH","RANGE","RAYON","RELAY","RELIC","RENEW","RESIN","RHYME","RIDGE","RODEO","RUMOR","RURAL","SALON","SAUCE","SCARF","SCENT","SCORE","SCRUB","SCUBA","SHELL","SHIFT","SHORE","SHRUB","SIEVE","SKULL","SLICE","SLIDE","SLOTH","SNACK","SOLID","SOUND","SPACE","SPARE","SPARK","SPICE","SPILL","SPINY","SPLIT","SQUID","STACK","STAGE","STAIN","STAKE","STARK","STASH","STATE","STEEP","STEER","STICK","STING","STOCK","SURGE","SWAMP","SWEAR","SWEAT","SWEEP","SWING","SYRUP","THORN","TOXIC","TRACT","TREAD","TUTOR","TWINE","TWIST","UNIFY","UNITY","UTTER","VAPOR","VIRAL","VIRUS","VOCAL","WAGON","WISPY","WRECK","YEAST","YIELD"] \ No newline at end of file diff --git a/lib/game/2048/card.dart b/lib/game/2048/card.dart deleted file mode 100644 index 38d8f7549..000000000 --- a/lib/game/2048/card.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/animation/number.dart'; -import 'package:mimir/game/2048/storage.dart'; -import 'package:mimir/game/widget/card.dart'; - -import 'i18n.dart'; - -class GameAppCard2048 extends StatefulWidget { - const GameAppCard2048({ - super.key, - }); - - @override - State createState() => _GameAppCard2048State(); -} - -class _GameAppCard2048State extends State { - @override - Widget build(BuildContext context) { - return OfflineGameAppCard( - name: i18n.title, - baseRoute: "/2048", - save: Storage2048.save, - supportRecords: true, - view: const BestScore2048Card(), - ); - } -} - -class BestScore2048Card extends ConsumerWidget { - const BestScore2048Card({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final best = ref.watch(Storage2048.record.$bestScore); - final maxNumber = ref.watch(Storage2048.record.$maxNumber); - return [ - Card( - child: ListTile( - leading: const Icon(Icons.score), - title: i18n.best.text(maxLines: 1), - subtitle: AnimatedNumber( - value: best, - builder: (ctx, v) => "${v.toInt()}".text(), - ), - ), - ).expanded(), - Card( - child: ListTile( - leading: const Icon(Icons.numbers), - title: i18n.max.text(maxLines: 1), - subtitle: AnimatedNumber( - value: maxNumber, - builder: (ctx, v) => "${v.toInt()}".text(), - ), - ), - ).expanded(), - ].row(maa: MainAxisAlignment.spaceEvenly, mas: MainAxisSize.min, caa: CrossAxisAlignment.start); - } -} diff --git a/lib/game/2048/entity/board.dart b/lib/game/2048/entity/board.dart deleted file mode 100644 index 250139d51..000000000 --- a/lib/game/2048/entity/board.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:copy_with_extension/copy_with_extension.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import 'package:uuid/uuid.dart'; - -import '../entity/tile.dart'; -import 'save.dart'; - -part "board.g.dart"; - -@CopyWith(skipFields: true) -class Board { - //Current score on the board - final int score; - - //Current list of tiles shown on the board - final List tiles; - - final GameStatus status; - - /// Create a model for a new game. - const Board({ - this.score = 0, - required this.tiles, - this.status = GameStatus.idle, - }); - - /// Create a Board from json data - factory Board.fromSave(Save2048 save) { - final tiles = []; - for (var i = 0; i < save.tiles.length; i++) { - final score = save.tiles[i]; - if (score > 0) { - tiles.add(Tile(id: const Uuid().v4(), value: score, index: i)); - } - } - return Board(score: save.score, tiles: tiles); - } - - //Generate json data from the Board - Save2048 toSave() { - final slots = List.generate(16, (index) => -1, growable: false); - for (final tile in tiles) { - slots[tile.index] = tile.value; - } - return Save2048( - score: score, - tiles: slots, - ); - } - - int get maxNumber => tiles.map((tile) => tile.value).maxOrNull ?? 0; -} diff --git a/lib/game/2048/entity/board.g.dart b/lib/game/2048/entity/board.g.dart deleted file mode 100644 index df69854b7..000000000 --- a/lib/game/2048/entity/board.g.dart +++ /dev/null @@ -1,63 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'board.dart'; - -// ************************************************************************** -// CopyWithGenerator -// ************************************************************************** - -abstract class _$BoardCWProxy { - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// Board(...).copyWith(id: 12, name: "My name") - /// ```` - Board call({ - int? score, - List? tiles, - GameStatus? status, - }); -} - -/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfBoard.copyWith(...)`. -class _$BoardCWProxyImpl implements _$BoardCWProxy { - const _$BoardCWProxyImpl(this._value); - - final Board _value; - - @override - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// Board(...).copyWith(id: 12, name: "My name") - /// ```` - Board call({ - Object? score = const $CopyWithPlaceholder(), - Object? tiles = const $CopyWithPlaceholder(), - Object? status = const $CopyWithPlaceholder(), - }) { - return Board( - score: score == const $CopyWithPlaceholder() || score == null - ? _value.score - // ignore: cast_nullable_to_non_nullable - : score as int, - tiles: tiles == const $CopyWithPlaceholder() || tiles == null - ? _value.tiles - // ignore: cast_nullable_to_non_nullable - : tiles as List, - status: status == const $CopyWithPlaceholder() || status == null - ? _value.status - // ignore: cast_nullable_to_non_nullable - : status as GameStatus, - ); - } -} - -extension $BoardCopyWith on Board { - /// Returns a callable class that can be used as follows: `instanceOfBoard.copyWith(...)`. - // ignore: library_private_types_in_public_api - _$BoardCWProxy get copyWith => _$BoardCWProxyImpl(this); -} diff --git a/lib/game/2048/entity/record.dart b/lib/game/2048/entity/record.dart deleted file mode 100644 index 078285f97..000000000 --- a/lib/game/2048/entity/record.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/game/entity/record.dart'; -import 'package:uuid/uuid.dart'; - -part "record.g.dart"; - -@JsonSerializable() -class Record2048 extends GameRecord { - final int score; - final int maxNumber; - - const Record2048({ - required super.uuid, - required super.ts, - required this.score, - required this.maxNumber, - }); - - factory Record2048.createFrom({ - required int score, - required int maxNumber, - }) { - return Record2048( - uuid: const Uuid().v4(), - ts: DateTime.now(), - maxNumber: maxNumber, - score: score, - ); - } - - bool get hasVictory => maxNumber >= 2048; - - Map toJson() => _$Record2048ToJson(this); - - factory Record2048.fromJson(Map json) => _$Record2048FromJson(json); -} diff --git a/lib/game/2048/entity/record.g.dart b/lib/game/2048/entity/record.g.dart deleted file mode 100644 index f8454eb4e..000000000 --- a/lib/game/2048/entity/record.g.dart +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'record.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Record2048 _$Record2048FromJson(Map json) => Record2048( - uuid: json['uuid'] as String? ?? genUuidV4(), - ts: DateTime.parse(json['ts'] as String), - score: (json['score'] as num).toInt(), - maxNumber: (json['maxNumber'] as num).toInt(), - ); - -Map _$Record2048ToJson(Record2048 instance) => { - 'uuid': instance.uuid, - 'ts': instance.ts.toIso8601String(), - 'score': instance.score, - 'maxNumber': instance.maxNumber, - }; diff --git a/lib/game/2048/entity/save.dart b/lib/game/2048/entity/save.dart deleted file mode 100644 index eacffeb3e..000000000 --- a/lib/game/2048/entity/save.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part "save.g.dart"; - -List _defaultTiles() { - return List.generate(16, (index) => -1); -} - -@JsonSerializable() -class Save2048 { - @JsonKey(defaultValue: 0) - final int score; - @JsonKey(defaultValue: _defaultTiles) - final List tiles; - - const Save2048({ - required this.score, - required this.tiles, - }); - - Map toJson() => _$Save2048ToJson(this); - - factory Save2048.fromJson(Map json) => _$Save2048FromJson(json); -} diff --git a/lib/game/2048/entity/save.g.dart b/lib/game/2048/entity/save.g.dart deleted file mode 100644 index 112016c0d..000000000 --- a/lib/game/2048/entity/save.g.dart +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'save.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Save2048 _$Save2048FromJson(Map json) => Save2048( - score: (json['score'] as num?)?.toInt() ?? 0, - tiles: (json['tiles'] as List?)?.map((e) => (e as num).toInt()).toList() ?? _defaultTiles(), - ); - -Map _$Save2048ToJson(Save2048 instance) => { - 'score': instance.score, - 'tiles': instance.tiles, - }; diff --git a/lib/game/2048/entity/tile.dart b/lib/game/2048/entity/tile.dart deleted file mode 100644 index 597754d89..000000000 --- a/lib/game/2048/entity/tile.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:copy_with_extension/copy_with_extension.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'tile.g.dart'; - -@JsonSerializable(anyMap: true) -@CopyWith(skipFields: true) -class Tile { - //Unique id used as ValueKey for the TileWidget - final String id; - - //The number on the tile - final int value; - - //The index of the tile on the board from which the position of the tile will be calculated - final int index; - - //The next index of the tile on the board - final int? nextIndex; - - //Whether the tile was merged with another tile - final bool merged; - - const Tile({ - required this.id, - required this.value, - required this.index, - this.nextIndex, - this.merged = false, - }); - - //Calculate the current top position based on the current index - double getTop(double size) { - var i = ((index + 1) / 4).ceil(); - return ((i - 1) * size) + (12.0 * i); - } - - //Calculate the current left position based on the current index - double getLeft(double size) { - var i = (index - (((index + 1) / 4).ceil() * 4 - 4)); - return (i * size) + (12.0 * (i + 1)); - } - - //Calculate the next top position based on the next index - double? getNextTop(double size) { - if (nextIndex == null) return null; - var i = ((nextIndex! + 1) / 4).ceil(); - return ((i - 1) * size) + (12.0 * i); - } - - //Calculate the next top position based on the next index - double? getNextLeft(double size) { - if (nextIndex == null) return null; - var i = (nextIndex! - (((nextIndex! + 1) / 4).ceil() * 4 - 4)); - return (i * size) + (12.0 * (i + 1)); - } - - //Create a Tile from json data - factory Tile.fromJson(Map json) => _$TileFromJson(json); - - //Generate json data from the Tile - Map toJson() => _$TileToJson(this); -} diff --git a/lib/game/2048/entity/tile.g.dart b/lib/game/2048/entity/tile.g.dart deleted file mode 100644 index 7585edcd7..000000000 --- a/lib/game/2048/entity/tile.g.dart +++ /dev/null @@ -1,95 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'tile.dart'; - -// ************************************************************************** -// CopyWithGenerator -// ************************************************************************** - -abstract class _$TileCWProxy { - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// Tile(...).copyWith(id: 12, name: "My name") - /// ```` - Tile call({ - String? id, - int? value, - int? index, - int? nextIndex, - bool? merged, - }); -} - -/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfTile.copyWith(...)`. -class _$TileCWProxyImpl implements _$TileCWProxy { - const _$TileCWProxyImpl(this._value); - - final Tile _value; - - @override - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// Tile(...).copyWith(id: 12, name: "My name") - /// ```` - Tile call({ - Object? id = const $CopyWithPlaceholder(), - Object? value = const $CopyWithPlaceholder(), - Object? index = const $CopyWithPlaceholder(), - Object? nextIndex = const $CopyWithPlaceholder(), - Object? merged = const $CopyWithPlaceholder(), - }) { - return Tile( - id: id == const $CopyWithPlaceholder() || id == null - ? _value.id - // ignore: cast_nullable_to_non_nullable - : id as String, - value: value == const $CopyWithPlaceholder() || value == null - ? _value.value - // ignore: cast_nullable_to_non_nullable - : value as int, - index: index == const $CopyWithPlaceholder() || index == null - ? _value.index - // ignore: cast_nullable_to_non_nullable - : index as int, - nextIndex: nextIndex == const $CopyWithPlaceholder() - ? _value.nextIndex - // ignore: cast_nullable_to_non_nullable - : nextIndex as int?, - merged: merged == const $CopyWithPlaceholder() || merged == null - ? _value.merged - // ignore: cast_nullable_to_non_nullable - : merged as bool, - ); - } -} - -extension $TileCopyWith on Tile { - /// Returns a callable class that can be used as follows: `instanceOfTile.copyWith(...)`. - // ignore: library_private_types_in_public_api - _$TileCWProxy get copyWith => _$TileCWProxyImpl(this); -} - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Tile _$TileFromJson(Map json) => Tile( - id: json['id'] as String, - value: (json['value'] as num).toInt(), - index: (json['index'] as num).toInt(), - nextIndex: (json['nextIndex'] as num?)?.toInt(), - merged: json['merged'] as bool? ?? false, - ); - -Map _$TileToJson(Tile instance) => { - 'id': instance.id, - 'value': instance.value, - 'index': instance.index, - 'nextIndex': instance.nextIndex, - 'merged': instance.merged, - }; diff --git a/lib/game/2048/i18n.dart b/lib/game/2048/i18n.dart deleted file mode 100644 index e9e692275..000000000 --- a/lib/game/2048/i18n.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:mimir/game/i18n.dart'; -import 'package:mimir/l10n/common.dart'; - -import 'r.dart'; - -const i18n = _I18n(); - -class _I18n with CommonI18nMixin, CommonGameI18nMixin { - const _I18n(); - - static const ns = "game.${R2048.name}"; - final records = const _Records(); - - String get title => "$ns.title".tr(); - - String get score => "$ns.score".tr(); - - String get best => "$ns.best".tr(); - - String get max => "$ns.max".tr(); -} - -class _Records { - static const ns = "${_I18n.ns}.records"; - - const _Records(); - - String get title => "$ns.title".tr(); - - String record({ - required int maxNumber, - required int score, - }) => - "$ns.record".tr(namedArgs: { - "maxNumber": "$maxNumber", - "score": "$score", - }); -} diff --git a/lib/game/2048/manager/logic.dart b/lib/game/2048/manager/logic.dart deleted file mode 100644 index 62371ef9a..000000000 --- a/lib/game/2048/manager/logic.dart +++ /dev/null @@ -1,327 +0,0 @@ -import 'dart:math'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import 'package:mimir/game/utils.dart'; -import 'package:uuid/uuid.dart'; - -import '../entity/record.dart'; -import '../entity/tile.dart'; -import '../entity/board.dart'; - -import '../storage.dart'; -import 'next_direction.dart'; -import 'round.dart'; - -class GameLogic extends StateNotifier { - // We will use this list to retrieve the right index when user swipes up/down - // which will allow us to reuse most of the logic. - static const verticalOrder = [12, 8, 4, 0, 13, 9, 5, 1, 14, 10, 6, 2, 15, 11, 7, 3]; - - final StateNotifierProviderRef ref; - - GameLogic(this.ref) : super(const Board(tiles: [])); - - // Start New Game - void newGame() { - state = Board(tiles: [random([])]); - } - - // Continue from save - void fromSave(Board save) { - state = save; - } - - // Check whether the indexes are in the same row or column in the board. - static bool _inRange(index, nextIndex) { - return index < 4 && nextIndex < 4 || - index >= 4 && index < 8 && nextIndex >= 4 && nextIndex < 8 || - index >= 8 && index < 12 && nextIndex >= 8 && nextIndex < 12 || - index >= 12 && nextIndex >= 12; - } - - static Tile _calculate(Tile tile, List tiles, direction) { - bool asc = direction == SwipeDirection.left || direction == SwipeDirection.up; - bool vert = direction == SwipeDirection.up || direction == SwipeDirection.down; - // Get the first index from the left in the row - // Example: for left swipe that can be: 0, 4, 8, 12 - // for right swipe that can be: 3, 7, 11, 15 - // depending which row in the column in the board we need - // let's say the title.index = 6 (which is the 3rd tile from the left and 2nd from right side, in the second row) - // ceil means it will ALWAYS round up to the next largest integer - // NOTE: don't confuse ceil it with floor or round as even if the value is 2.1 output would be 3. - // ((6 + 1) / 4) = 1.75 - // Ceil(1.75) = 2 - // If it's ascending: 2 * 4 – 4 = 4, which is the first index from the left side in the second row - // If it's descending: 2 * 4 – 1 = 7, which is the last index from the left side and first index from the right side in the second row - // If user swipes vertically use the verticalOrder list to retrieve the up/down index else use the existing index - int index = vert ? verticalOrder[tile.index] : tile.index; - int nextIndex = ((index + 1) / 4).ceil() * 4 - (asc ? 4 : 1); - - // If the list of the new tiles to be rendered is not empty get the last tile - // and if that tile is in the same row as the curren tile set the next index for the current tile to be after the last tile - if (tiles.isNotEmpty) { - var last = tiles.last; - // If user swipes vertically use the verticalOrder list to retrieve the up/down index else use the existing index - var lastIndex = last.nextIndex ?? last.index; - lastIndex = vert ? verticalOrder[lastIndex] : lastIndex; - if (_inRange(index, lastIndex)) { - // If the order is ascending set the tile after the last processed tile - // If the order is descending set the tile before the last processed tile - nextIndex = lastIndex + (asc ? 1 : -1); - } - } - - // Return immutable copy of the current tile with the new next index - // which can either be the top left index in the row or the last tile nextIndex/index + 1 - return tile.copyWith(nextIndex: vert ? verticalOrder.indexOf(nextIndex) : nextIndex); - } - - //Move the tile in the direction - bool move(SwipeDirection direction) { - bool asc = direction == SwipeDirection.left || direction == SwipeDirection.up; - bool vert = direction == SwipeDirection.up || direction == SwipeDirection.down; - // Sort the list of tiles by index. - // If user swipes vertically use the verticalOrder list to retrieve the up/down index - state.tiles.sort( - ((a, b) => - (asc ? 1 : -1) * - (vert ? verticalOrder[a.index].compareTo(verticalOrder[b.index]) : a.index.compareTo(b.index))), - ); - - List tiles = []; - - for (int i = 0, l = state.tiles.length; i < l; i++) { - var tile = state.tiles[i]; - - // Calculate nextIndex for current tile. - tile = _calculate(tile, tiles, direction); - tiles.add(tile); - - if (i + 1 < l) { - var next = state.tiles[i + 1]; - // Assign current tile nextIndex or index to the next tile if its allowed to be moved. - if (tile.value == next.value) { - // If user swipes vertically use the verticalOrder list to retrieve the up/down index else use the existing index - var index = vert ? verticalOrder[tile.index] : tile.index, - nextIndex = vert ? verticalOrder[next.index] : next.index; - if (_inRange(index, nextIndex)) { - tiles.add(next.copyWith(nextIndex: tile.nextIndex)); - // Skip next iteration if next tile was already assigned nextIndex. - i += 1; - continue; - } - } - } - } - - // Assign immutable copy of the new board state and trigger rebuild. - state = state.copyWith(tiles: tiles); - return true; - } - - static final rand = Random(); - - /// Generates tiles at random place on the board. - /// Avoids occupied tiles. - static Tile random(List indexes) { - final candidates = Iterable.generate(16, (i) => i).toList(); - candidates.removeWhere((i) => indexes.contains(i)); - final index = candidates[rand.nextInt(candidates.length)]; - return Tile(id: const Uuid().v4(), value: 2, index: index); - } - - //Merge tiles - void merge() { - List tiles = []; - var tilesMoved = false; - List indexes = []; - var score = state.score; - - for (int i = 0, l = state.tiles.length; i < l; i++) { - var tile = state.tiles[i]; - - var value = tile.value, merged = false; - - if (i + 1 < l) { - //sum the number of the two tiles with same index and mark the tile as merged and skip the next iteration. - var next = state.tiles[i + 1]; - if (tile.nextIndex == next.nextIndex || tile.index == next.nextIndex && tile.nextIndex == null) { - value = tile.value + next.value; - merged = true; - applyGameHapticFeedback(); - score += tile.value; - i += 1; - } - } - - if (merged || tile.nextIndex != null && tile.index != tile.nextIndex) { - tilesMoved = true; - } - - tiles.add(tile.copyWith(index: tile.nextIndex ?? tile.index, nextIndex: null, value: value, merged: merged)); - indexes.add(tiles.last.index); - } - - // If tiles got moved then generate a new tile at random position of the available positions on the board. - // If all tiles are occupied, then don't generate. - if (tilesMoved && indexes.length < 16) { - tiles.add(random(indexes)); - } - state = state.copyWith(score: score, tiles: tiles); - } - - //Finish round, win or loose the game. - void _endRound() { - var boardFilled = true; - var got2048 = false; - List tiles = []; - - //If there is no more empty place on the board - if (state.tiles.length == 16) { - state.tiles.sort(((a, b) => a.index.compareTo(b.index))); - - for (int i = 0, l = state.tiles.length; i < l; i++) { - var tile = state.tiles[i]; - - //If there is a tile with 2048 then the game is won. - if (tile.value == 2048) { - got2048 = true; - } - - var x = (i - (((i + 1) / 4).ceil() * 4 - 4)); - - if (x > 0 && i - 1 >= 0) { - //If tile can be merged with left tile then game is not lost. - var left = state.tiles[i - 1]; - if (tile.value == left.value) { - boardFilled = false; - } - } - - if (x < 3 && i + 1 < l) { - //If tile can be merged with right tile then game is not lost. - var right = state.tiles[i + 1]; - if (tile.value == right.value) { - boardFilled = false; - } - } - - if (i - 4 >= 0) { - //If tile can be merged with above tile then game is not lost. - var top = state.tiles[i - 4]; - if (tile.value == top.value) { - boardFilled = false; - } - } - - if (i + 4 < l) { - //If tile can be merged with the bellow tile then game is not lost. - var bottom = state.tiles[i + 4]; - if (tile.value == bottom.value) { - boardFilled = false; - } - } - //Set the tile merged: false - tiles.add(tile.copyWith(merged: false)); - } - } else { - //There is still a place on the board to add a tile so the game is not lost. - boardFilled = false; - for (var tile in state.tiles) { - //If there is a tile with 2048 then the game is won. - if (tile.value == 2048) { - got2048 = true; - } - //Set the tile merged: false - tiles.add(tile.copyWith(merged: false)); - } - } - state = state.copyWith( - tiles: tiles, - ); - if (boardFilled) { - if (got2048) { - onVictory(); - } else { - onGameOver(); - } - } - } - - //Mark the merged as false after the merge animation is complete. - bool endRound() { - if (!state.status.canPlay) return false; - //End round. - _endRound(); - ref.read(roundManager.notifier).end(); - - //If player moved too fast before the current animation/transition finished, start the move for the next direction - var nextDirection = ref.read(nextDirectionManager); - if (nextDirection != null) { - move(nextDirection); - ref.read(nextDirectionManager.notifier).clear(); - return true; - } - return false; - } - - //Move the tiles using the arrow keys on the keyboard. - bool onKey(KeyEvent event) { - if (!state.status.canPlay) return false; - SwipeDirection? direction; - if (HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.arrowRight)) { - direction = SwipeDirection.right; - } else if (HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.arrowLeft)) { - direction = SwipeDirection.left; - } else if (HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.arrowUp)) { - direction = SwipeDirection.up; - } else if (HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.arrowDown)) { - direction = SwipeDirection.down; - } - - if (direction != null) { - move(direction); - return true; - } - return false; - } - - void onVictory() { - if (!state.status.canPlay) return; - state = state.copyWith( - status: GameStatus.victory, - ); - Storage2048.record.add(Record2048.createFrom( - maxNumber: state.maxNumber, - score: state.score, - )); - } - - void onGameOver() { - if (!state.status.canPlay) return; - state = state.copyWith( - status: GameStatus.gameOver, - ); - Storage2048.record.add(Record2048.createFrom( - maxNumber: state.maxNumber, - score: state.score, - )); - } - - bool canSave() { - if (!state.status.shouldSave) return false; - if (state.tiles.isEmpty) return false; - if (state.tiles.length == 1 && state.tiles.first.value == 2) return false; - return true; - } - - Future save() async { - if (canSave()) { - await Storage2048.save.save(state.toSave()); - } else { - await Storage2048.save.delete(); - } - } -} diff --git a/lib/game/2048/manager/next_direction.dart b/lib/game/2048/manager/next_direction.dart deleted file mode 100644 index 859208469..000000000 --- a/lib/game/2048/manager/next_direction.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; - -/* -In case user swipes too fast we prevent for the next round to start until the current round finishes, we do that using the RoundManager, -but instead of canceling that round we will queue it so that the round automatically starts as soon the current finishes, -that way we will prevent the user feeling like the game is lag-ish or slow. -*/ -class NextDirectionManager extends StateNotifier { - NextDirectionManager() : super(null); - - void queue(direction) { - state = direction; - } - - void clear() { - state = null; - } -} - -final nextDirectionManager = StateNotifierProvider((ref) { - return NextDirectionManager(); -}); diff --git a/lib/game/2048/manager/round.dart b/lib/game/2048/manager/round.dart deleted file mode 100644 index 9eaa140d0..000000000 --- a/lib/game/2048/manager/round.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -/* -A Notifier when a round starts, in order to prevent the next round starts before the current ends -prevents animation issues when user tries to move tiles too soon. -*/ -class RoundManager extends StateNotifier { - RoundManager() : super(true); - - void end() { - state = true; - } - - void begin() { - state = false; - } -} - -final roundManager = StateNotifierProvider((ref) { - return RoundManager(); -}); diff --git a/lib/game/2048/page/game.dart b/lib/game/2048/page/game.dart deleted file mode 100644 index c03e33abe..000000000 --- a/lib/game/2048/page/game.dart +++ /dev/null @@ -1,211 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_swipe_detector/flutter_swipe_detector.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/adaptive/multiplatform.dart'; -import 'package:mimir/game/2048/storage.dart'; -import 'package:mimir/game/ability/ability.dart'; -import 'package:mimir/game/ability/autosave.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import 'package:mimir/game/widget/party_popper.dart'; - -import '../entity/board.dart'; -import '../widget/empty_board.dart'; -import '../widget/modal.dart'; -import '../widget/score_board.dart'; -import '../widget/tile_board.dart'; -import '../theme.dart'; -import '../manager/logic.dart'; -import '../i18n.dart'; - -final state2048 = StateNotifierProvider((ref) { - return GameLogic(ref); -}); - -class Game2048 extends ConsumerStatefulWidget { - final bool newGame; - - const Game2048({ - super.key, - this.newGame = true, - }); - - @override - ConsumerState createState() => _GameState(); -} - -class _GameState extends ConsumerState - with TickerProviderStateMixin, WidgetsBindingObserver, GameWidgetAbilityMixin { - //The controller used to move the the tiles - late final AnimationController _moveController = AnimationController( - duration: const Duration(milliseconds: 100), - vsync: this, - )..addStatusListener((status) { - //When the movement finishes merge the tiles and start the scale animation which gives the pop effect. - if (status == AnimationStatus.completed) { - ref.read(state2048.notifier).merge(); - _scaleController.forward(from: 0.0); - } - }); - - //The curve animation for the move animation controller. - late final CurvedAnimation _moveAnimation = CurvedAnimation( - parent: _moveController, - curve: Curves.easeInOut, - ); - - //The controller used to show a popup effect when the tiles get merged - late final AnimationController _scaleController = AnimationController( - duration: const Duration(milliseconds: 200), - vsync: this, - )..addStatusListener((status) { - //When the scale animation finishes end the round and if there is a queued movement start the move controller again for the next direction. - if (status == AnimationStatus.completed) { - if (ref.read(state2048.notifier).endRound()) { - _moveController.forward(from: 0.0); - } - } - }); - - //The curve animation for the scale animation controller. - late final CurvedAnimation _scaleAnimation = CurvedAnimation( - parent: _scaleController, - curve: Curves.easeInOut, - ); - final $focus = FocusNode(); - - @override - List createAbility() => [AutoSaveWidgetAbility(onSave: onSave)]; - - @override - void initState() { - super.initState(); - //Add an Observer for the Lifecycles of the App - WidgetsBinding.instance.endOfFrame.then((_) { - if (widget.newGame) { - ref.read(state2048.notifier).newGame(); - } else { - final save = Storage2048.save.load(); - if (save != null) { - ref.read(state2048.notifier).fromSave(Board.fromSave(save)); - } else { - ref.read(state2048.notifier).newGame(); - } - } - }); - } - - @override - void dispose() { - $focus.dispose(); - //Dispose the animations. - _moveAnimation.dispose(); - _scaleAnimation.dispose(); - _moveController.dispose(); - _scaleController.dispose(); - super.dispose(); - } - - void onSave() { - ref.read(state2048.notifier).save(); - } - - @override - Widget build(BuildContext context) { - final gameStatus = ref.watch(state2048.select((state) => state.status)); - return Scaffold( - backgroundColor: backgroundColor, - appBar: AppBar( - title: i18n.title.text(), - actions: [ - PlatformIconButton( - onPressed: () { - ref.read(state2048.notifier).newGame(); - }, - icon: Icon(context.icons.refresh), - ) - ], - ), - body: KeyboardListener( - autofocus: true, - focusNode: $focus, - onKeyEvent: (event) { - if (!ref.read(state2048).status.canPlay) return; - //Move the tile with the arrows on the keyboard on Desktop - if (!_moveController.isCompleted) return; - if (ref.read(state2048.notifier).onKey(event)) { - _moveController.forward(from: 0.0); - } - }, - child: SwipeDetector( - onSwipe: (direction, offset) { - if (!ref.read(state2048).status.canPlay) return; - if (ref.read(state2048.notifier).move(direction)) { - _moveController.forward(from: 0.0); - } - }, - child: [ - buildBody(), - VictoryPartyPopper( - pop: gameStatus == GameStatus.victory, - ), - if (gameStatus == GameStatus.victory || gameStatus == GameStatus.gameOver) - const Positioned.fill( - child: GameOverModal(), - ), - ].stack(), - ), - ), - ); - } - - Widget buildScoreBoardArea() { - return [ - Text( - i18n.title, - style: const TextStyle( - color: textColor, - fontWeight: FontWeight.bold, - fontSize: 52.0, - ), - ), - const ScoreBoard(), - ].row(maa: MainAxisAlignment.spaceBetween).padSymmetric(h: 16.0, v: 32); - } - - Widget buildGameArea() { - return Stack( - children: [ - const EmptyBoardWidget(), - TileBoardWidget( - moveAnimation: _moveAnimation, - scaleAnimation: _scaleAnimation, - ), - ], - ); - } - - Widget buildBody() { - if (context.isPortrait) { - return [ - buildScoreBoardArea(), - buildGameArea().padAll(8), - ].column( - caa: CrossAxisAlignment.center, - maa: MainAxisAlignment.center, - ); - } else { - return [ - buildScoreBoardArea().expanded(), - buildGameArea().expanded(), - ] - .row( - caa: CrossAxisAlignment.start, - maa: MainAxisAlignment.center, - ) - .center(); - } - } -} diff --git a/lib/game/2048/page/index.dart b/lib/game/2048/page/index.dart deleted file mode 100644 index 98e498ff8..000000000 --- a/lib/game/2048/page/index.dart +++ /dev/null @@ -1,26 +0,0 @@ -// thanks to https://github.com/angjelkom/flutter_2048 -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import 'game.dart'; - -class Game2048Page extends StatefulWidget { - final bool newGame; - - const Game2048Page({ - super.key, - this.newGame = true, - }); - - @override - State createState() => _Game2048PageState(); -} - -class _Game2048PageState extends State { - @override - Widget build(BuildContext context) { - return ProviderScope( - child: Game2048(newGame: widget.newGame), - ); - } -} diff --git a/lib/game/2048/page/records.dart b/lib/game/2048/page/records.dart deleted file mode 100644 index 5736fcf1c..000000000 --- a/lib/game/2048/page/records.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/game/page/records.dart'; -import 'package:mimir/l10n/extension.dart'; - -import '../storage.dart'; -import '../entity/record.dart'; -import '../i18n.dart'; - -class Records2048Page extends ConsumerStatefulWidget { - const Records2048Page({super.key}); - - @override - ConsumerState createState() => _Records2048PageState(); -} - -class _Records2048PageState extends ConsumerState { - @override - Widget build(BuildContext context) { - return GameRecordsPage( - title: i18n.records.title, - recordStorage: Storage2048.record, - itemBuilder: (context, record) { - return Record2048Tile(record: record); - }, - ); - } -} - -class Record2048Tile extends StatelessWidget { - final Record2048 record; - - const Record2048Tile({ - super.key, - required this.record, - }); - - @override - Widget build(BuildContext context) { - return ListTile( - leading: Icon( - record.hasVictory ? Icons.check : Icons.clear, - color: record.hasVictory ? Colors.green : Colors.red, - ), - title: i18n.records - .record( - maxNumber: record.maxNumber, - score: record.score, - ) - .text(), - subtitle: [ - context.formatYmdhmsNum(record.ts).text(), - // record.blueprint.text(), - ].column(caa: CrossAxisAlignment.start), - ); - } -} diff --git a/lib/game/2048/r.dart b/lib/game/2048/r.dart deleted file mode 100644 index 5f0471593..000000000 --- a/lib/game/2048/r.dart +++ /dev/null @@ -1,4 +0,0 @@ -class R2048 { - static const name = "2048"; - static const version = 1; -} diff --git a/lib/game/2048/settings.dart b/lib/game/2048/settings.dart deleted file mode 100644 index 74490a1c3..000000000 --- a/lib/game/2048/settings.dart +++ /dev/null @@ -1,3 +0,0 @@ -class Settings2048 { - const Settings2048(); -} diff --git a/lib/game/2048/storage.dart b/lib/game/2048/storage.dart deleted file mode 100644 index b7ef231a0..000000000 --- a/lib/game/2048/storage.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:mimir/game/storage/record.dart'; -import 'package:mimir/game/storage/save.dart'; -import 'package:mimir/storage/hive/init.dart'; -import 'package:mimir/utils/hive.dart'; - -import 'entity/record.dart'; -import 'entity/save.dart'; -import 'r.dart'; - -class Storage2048 { - static const _ns = "/${R2048.name}/${R2048.version}"; - static final save = GameSaveStorage( - () => HiveInit.game2048, - prefix: _ns, - serialize: (save) => save.toJson(), - deserialize: Save2048.fromJson, - ); - static final record = RecordStorage2048( - () => HiveInit.game2048, - prefix: _ns, - serialize: (record) => record.toJson(), - deserialize: Record2048.fromJson, - ); -} - -class RecordStorage2048 extends GameRecordStorage { - RecordStorage2048( - super.box, { - required super.prefix, - required super.serialize, - required super.deserialize, - }); - - String get _kBestScore => "$prefix/bestScore"; - - int get bestScore => box().safeGet(_kBestScore) ?? 0; - - set bestScore(int newValue) => box().safePut(_kBestScore, newValue); - - String get _kMaxNumber => "$prefix/maxNumber"; - - int get maxNumber => box().safeGet(_kMaxNumber) ?? 0; - - set maxNumber(int newValue) => box().safePut(_kMaxNumber, newValue); - - @override - String add(Record2048 save) { - final id = super.add(save); - if (save.score > bestScore) { - bestScore = save.score; - } - if (save.maxNumber > maxNumber) { - maxNumber = save.maxNumber; - } - return id; - } - - @override - void clear() { - super.clear(); - box().delete(_kBestScore); - box().delete(_kMaxNumber); - } - - late final $bestScore = box().providerWithDefault( - _kBestScore, - () => 0, - ); - late final $maxNumber = box().providerWithDefault( - _kMaxNumber, - () => 0, - ); -} diff --git a/lib/game/2048/theme.dart b/lib/game/2048/theme.dart deleted file mode 100644 index 07b142443..000000000 --- a/lib/game/2048/theme.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:ui'; - -const backgroundColor = Color(0xfffaf8ef); -const textColor = Color(0xff776e65); -const textColorWhite = Color(0xfff9f6f2); -const boardColor = Color(0xffbbada0); -const emptyTileColor = Color(0xffcdc1b4); -const buttonColor = Color(0xff8f7a66); -const scoreColor = Color(0xffbbada0); -const overlayColor = Color.fromRGBO(238, 228, 218, 0.73); -const defaultTileColor = Color(0xff121212); - -const tileColors = { - 2: Color(0xffeee4da), - 4: Color(0xffeee1c9), - 8: Color(0xfff3b27a), - 16: Color(0xfff69664), - 32: Color(0xfff77c5f), - 64: Color(0xfff75f3b), - 128: Color(0xffedd073), - 256: Color(0xffedcc62), - 512: Color(0xffedc950), - 1024: Color(0xffedc53f), - 2048: Color(0xffedc22e), -}; diff --git a/lib/game/2048/widget/animated_tile.dart b/lib/game/2048/widget/animated_tile.dart deleted file mode 100644 index 61e9a599f..000000000 --- a/lib/game/2048/widget/animated_tile.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../entity/tile.dart'; - -class AnimatedTile extends AnimatedWidget { - //We use Listenable.merge in order to update the animated widget when both of the controllers have change - AnimatedTile({ - super.key, - required this.moveAnimation, - required this.scaleAnimation, - required this.tile, - required this.child, - required this.size, - }) : super(listenable: Listenable.merge([moveAnimation, scaleAnimation])); - - final Tile tile; - final Widget child; - final CurvedAnimation moveAnimation; - final CurvedAnimation scaleAnimation; - final double size; - - //Get the current top position based on current index of the tile - late final double _top = tile.getTop(size); - - //Get the current left position based on current index of the tile - late final double _left = tile.getLeft(size); - - //Get the next top position based on current next index of the tile - late final double _nextTop = tile.getNextTop(size) ?? _top; - - //Get the next top position based on next index of the tile - late final double _nextLeft = tile.getNextLeft(size) ?? _left; - - //top tween used to move the tile from top to bottom - late final Animation top = Tween( - begin: _top, - end: _nextTop, - ).animate( - moveAnimation, - ), - //left tween used to move the tile from left to right - left = Tween( - begin: _left, - end: _nextLeft, - ).animate( - moveAnimation, - ), - //scale tween used to use give "pop" effect when a merge happens - scale = TweenSequence( - >[ - TweenSequenceItem( - tween: Tween(begin: 1.0, end: 1.5).chain(CurveTween(curve: Curves.easeOut)), - weight: 50.0, - ), - TweenSequenceItem( - tween: Tween(begin: 1.5, end: 1.0).chain(CurveTween(curve: Curves.easeIn)), - weight: 50.0, - ), - ], - ).animate( - scaleAnimation, - ); - - @override - Widget build(BuildContext context) { - return Positioned( - top: top.value, - left: left.value, - //Only use scale animation if the tile was merged - child: tile.merged - ? ScaleTransition( - scale: scale, - child: child, - ) - : child, - ); - } -} diff --git a/lib/game/2048/widget/button.dart b/lib/game/2048/widget/button.dart deleted file mode 100644 index f432eed8f..000000000 --- a/lib/game/2048/widget/button.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -class ButtonWidget extends ConsumerWidget { - final String text; - final Color backgroundColor; - final VoidCallback onPressed; - - const ButtonWidget({ - super.key, - required this.text, - required this.onPressed, - required this.backgroundColor, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - //Button Widget with text for New Game and Try Again button. - return ElevatedButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(backgroundColor), - ), - onPressed: onPressed, - child: Text( - text, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18.0), - ), - ); - } -} diff --git a/lib/game/2048/widget/empty_board.dart b/lib/game/2048/widget/empty_board.dart deleted file mode 100644 index aab145383..000000000 --- a/lib/game/2048/widget/empty_board.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; - -import '../theme.dart'; -import 'tile.dart'; - -class EmptyBoardWidget extends StatelessWidget { - const EmptyBoardWidget({super.key}); - - @override - Widget build(BuildContext context) { - //Decides the maximum size the Board can be based on the shortest size of the screen. - final size = max(290.0, min((MediaQuery.of(context).size.shortestSide * 0.90).floorToDouble(), 460.0)); - - //Decide the size of the tile based on the size of the board minus the space between each tile. - final sizePerTile = (size / 4).floorToDouble(); - final tileSize = sizePerTile - 12.0 - (12.0 / 4); - final boardSize = sizePerTile * 4; - return Container( - width: boardSize, - height: boardSize, - decoration: BoxDecoration(color: boardColor, borderRadius: BorderRadius.circular(12.0)), - child: Stack( - children: List.generate(16, (i) { - //Render the empty board in 4x4 GridView - var x = ((i + 1) / 4).ceil(); - var y = x - 1; - - var top = y * (tileSize) + (x * 12.0); - var z = (i - (4 * y)); - var left = z * (tileSize) + ((z + 1) * 12.0); - - return Positioned( - top: top, - left: left, - child: BoardTile( - size: tileSize, - color: emptyTileColor, - ), - ); - }), - ), - ); - } -} diff --git a/lib/game/2048/widget/modal.dart b/lib/game/2048/widget/modal.dart deleted file mode 100644 index f12626598..000000000 --- a/lib/game/2048/widget/modal.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mimir/game/entity/game_status.dart'; - -import '../page/game.dart'; -import '../theme.dart'; -import '../i18n.dart'; -import 'button.dart'; - -class GameOverModal extends ConsumerWidget { - const GameOverModal({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final gameStatus = ref.watch(state2048.select((state) => state.status)); - return Container( - color: overlayColor, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - gameStatus == GameStatus.victory ? i18n.youWin : i18n.gameOver, - style: const TextStyle(color: textColor, fontWeight: FontWeight.bold, fontSize: 64.0), - ), - ButtonWidget( - text: gameStatus == GameStatus.gameOver ? i18n.newGame : i18n.tryAgain, - backgroundColor: buttonColor, - onPressed: () { - ref.read(state2048.notifier).newGame(); - }, - ) - ], - ), - ); - } -} diff --git a/lib/game/2048/widget/score_board.dart b/lib/game/2048/widget/score_board.dart deleted file mode 100644 index a0cce6cc1..000000000 --- a/lib/game/2048/widget/score_board.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/animation/number.dart'; -import 'package:mimir/game/2048/storage.dart'; -import '../page/game.dart'; -import '../i18n.dart'; - -import '../theme.dart'; - -class ScoreBoard extends ConsumerWidget { - const ScoreBoard({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final score = ref.watch(state2048.select((board) => board.score)); - var best = ref.watch(Storage2048.record.$bestScore); - best = max(best, score); - return [ - Score(label: i18n.score, score: score), - const SizedBox( - width: 8.0, - ), - Score(label: i18n.best, score: best), - ].row(maa: MainAxisAlignment.center, mas: MainAxisSize.min); - } -} - -class Score extends StatelessWidget { - final String label; - final int score; - - const Score({ - super.key, - required this.label, - required this.score, - }); - - @override - Widget build(BuildContext context) { - return Card.filled( - color: scoreColor, - child: [ - Text( - label.toUpperCase(), - style: const TextStyle(fontSize: 18.0, color: textColorWhite), - ), - AnimatedNumber( - value: score, - duration: Durations.short3, - builder: (ctx, score) => "${score.toInt()}".text( - style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 18.0), - ), - ), - ].column(mas: MainAxisSize.min).padSymmetric(h: 16, v: 8), - ); - } -} diff --git a/lib/game/2048/widget/tile.dart b/lib/game/2048/widget/tile.dart deleted file mode 100644 index 994fda1c1..000000000 --- a/lib/game/2048/widget/tile.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; - -class BoardTile extends StatelessWidget { - final Color? color; - final double size; - final Widget? child; - - const BoardTile({ - super.key, - this.color, - required this.size, - this.child, - }); - - @override - Widget build(BuildContext context) { - return Container( - width: size, - height: size, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(12.0), - ), - child: child, - ); - } -} diff --git a/lib/game/2048/widget/tile_board.dart b/lib/game/2048/widget/tile_board.dart deleted file mode 100644 index 5edb45495..000000000 --- a/lib/game/2048/widget/tile_board.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'dart:math'; -import 'package:auto_size_text/auto_size_text.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/game/2048/widget/tile.dart'; - -import '../page/game.dart'; -import '../theme.dart'; - -import 'animated_tile.dart'; - -class TileBoardWidget extends ConsumerWidget { - final CurvedAnimation moveAnimation; - final CurvedAnimation scaleAnimation; - - const TileBoardWidget({ - super.key, - required this.moveAnimation, - required this.scaleAnimation, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final board = ref.watch(state2048); - - //Decides the maximum size the Board can be based on the shortest size of the screen. - final size = max(290.0, min((MediaQuery.of(context).size.shortestSide * 0.90).floorToDouble(), 460.0)); - - //Decide the size of the tile based on the size of the board minus the space between each tile. - final sizePerTile = (size / 4).floorToDouble(); - final tileSize = sizePerTile - 12.0 - (12.0 / 4); - final boardSize = sizePerTile * 4; - return SizedBox( - width: boardSize, - height: boardSize, - child: Stack( - children: [ - ...List.generate( - board.tiles.length, - (i) { - var tile = board.tiles[i]; - - return AnimatedTile( - key: ValueKey(tile.id), - tile: tile, - moveAnimation: moveAnimation, - scaleAnimation: scaleAnimation, - size: tileSize, - //In order to optimize performances and prevent unneeded re-rendering the actual tile is passed as child to the AnimatedTile - //as the tile won't change for the duration of the movement (apart from it's position) - child: BoardTile( - size: tileSize, - color: tileColors[tile.value] ?? defaultTileColor, - child: AutoSizeText( - '${tile.value}', - maxLines: 1, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: context.textTheme.headlineLarge?.fontSize, - color: tile.value < 8 ? textColor : textColorWhite, - ), - ).center().padH(4), - ), - ); - }, - ), - ], - ), - ); - } -} diff --git a/lib/game/ability/ability.dart b/lib/game/ability/ability.dart deleted file mode 100644 index 098484d45..000000000 --- a/lib/game/ability/ability.dart +++ /dev/null @@ -1,66 +0,0 @@ -library ability; - -import 'package:flutter/cupertino.dart'; - -abstract class GameWidgetAbility { - const GameWidgetAbility(); - - void initState() {} - - void deactivate() {} - - void dispose() {} - - void onAppInactive() {} - - void onAppResumed() {} -} - -mixin GameWidgetAbilityMixin on State, WidgetsBindingObserver { - late final List abilities; - - /// This will be called in [initState]. - List createAbility(); - - @override - void initState() { - super.initState(); - abilities = createAbility(); - for (final ability in abilities) { - ability.initState(); - } - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - for (final ability in abilities) { - ability.dispose(); - } - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.inactive) { - for (final ability in abilities) { - ability.onAppInactive(); - } - } else if (state == AppLifecycleState.resumed) { - for (final ability in abilities) { - ability.onAppResumed(); - } - } - - super.didChangeAppLifecycleState(state); - } - - @override - void deactivate() { - for (final ability in abilities) { - ability.deactivate(); - } - super.deactivate(); - } -} diff --git a/lib/game/ability/autosave.dart b/lib/game/ability/autosave.dart deleted file mode 100644 index 8eb159637..000000000 --- a/lib/game/ability/autosave.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'ability.dart'; - -class AutoSaveWidgetAbility extends GameWidgetAbility { - final void Function() onSave; - - const AutoSaveWidgetAbility({required this.onSave}); - - @override - void onAppInactive() { - super.onAppInactive(); - // Save current state when the app becomes inactive - onSave(); - } - - @override - void deactivate() { - onSave(); - super.deactivate(); - } -} diff --git a/lib/game/ability/timer.dart b/lib/game/ability/timer.dart deleted file mode 100644 index f727868dc..000000000 --- a/lib/game/ability/timer.dart +++ /dev/null @@ -1,34 +0,0 @@ -import '../entity/timer.dart'; -import 'ability.dart'; - -class TimerWidgetAbility extends GameWidgetAbility { - late final GameTimer timer; - - @override - void initState() { - super.initState(); - timer = GameTimer(); - } - - @override - void dispose() { - timer.dispose(); - super.dispose(); - } - - @override - void deactivate() { - timer.stopTimer(); - super.deactivate(); - } - - @override - void onAppInactive() { - timer.pause(); - } - - @override - void onAppResumed() { - timer.resume(); - } -} diff --git a/lib/game/deep_link/blueprint.dart b/lib/game/deep_link/blueprint.dart deleted file mode 100644 index e2b1dce6b..000000000 --- a/lib/game/deep_link/blueprint.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/widgets.dart'; -import 'package:mimir/design/adaptive/dialog.dart'; -import 'package:mimir/intent/deep_link/protocol.dart'; -import 'package:mimir/r.dart'; - -import '../entity/blueprint.dart'; -import '../i18n.dart'; - -class GameBlueprintDeepLink implements DeepLinkHandlerProtocol { - static const host = "game"; - final String gameName; - final FutureOr Function({required BuildContext context, required String blueprint}) onHandleGameBlueprint; - - String get path => "/$gameName/blueprint"; - - const GameBlueprintDeepLink( - this.gameName, - this.onHandleGameBlueprint, - ); - - Uri encode(TBlueprint blueprint) => Uri( - scheme: R.scheme, - host: host, - path: path, - query: blueprint.build(), - ); - - Uri encodeString(String blueprint) => Uri( - scheme: R.scheme, - host: host, - path: path, - query: blueprint, - ); - - @override - bool match(Uri encoded) { - return encoded.host == host && encoded.path == path; - } - - @override - Future onHandle({ - required BuildContext context, - required Uri data, - }) async { - final blueprint = data.query; - final confirm = await context.showActionRequest( - action: i18n.loadGame, - desc: i18n.loadGameFromQrCode(gameName), - cancel: i18n.cancel, - ); - if (confirm != true) return; - if (!context.mounted) return; - await onHandleGameBlueprint( - context: context, - blueprint: blueprint, - ); - } -} diff --git a/lib/game/entity/blueprint.dart b/lib/game/entity/blueprint.dart deleted file mode 100644 index ab2f1bb2f..000000000 --- a/lib/game/entity/blueprint.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:meta/meta.dart'; - -@immutable -abstract class GameBlueprint { - const GameBlueprint(); - - String build(); - - TGame create(); -} diff --git a/lib/game/entity/game_mode.dart b/lib/game/entity/game_mode.dart deleted file mode 100644 index a2c000966..000000000 --- a/lib/game/entity/game_mode.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:meta/meta.dart'; - -abstract class GameMode { - final String name; - - @protected - const GameMode({ - required this.name, - }); - - String toJson() => name; - - @override - bool operator ==(Object other) { - return other is GameMode && runtimeType == other.runtimeType && name == other.name; - } - - @override - int get hashCode => name.hashCode; - - String l10n(); -} diff --git a/lib/game/entity/game_result.dart b/lib/game/entity/game_result.dart deleted file mode 100644 index 4a5b6eb8d..000000000 --- a/lib/game/entity/game_result.dart +++ /dev/null @@ -1,4 +0,0 @@ -enum GameResult { - victory, - gameOver; -} diff --git a/lib/game/entity/game_status.dart b/lib/game/entity/game_status.dart deleted file mode 100644 index a98c640fe..000000000 --- a/lib/game/entity/game_status.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -@JsonEnum() -enum GameStatus { - running, - idle, - gameOver, - victory; - - bool get shouldSave => this != gameOver && this != victory; - bool get canPlay => this != gameOver && this != victory; -} diff --git a/lib/game/entity/record.dart b/lib/game/entity/record.dart deleted file mode 100644 index 4a27bda57..000000000 --- a/lib/game/entity/record.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/entity/uuid.dart'; -import 'package:uuid/uuid.dart'; - -String genUuidV4() => const Uuid().v4(); - -class GameRecord implements WithUuid { - @JsonKey(defaultValue: genUuidV4) - @override - final String uuid; - @JsonKey() - final DateTime ts; - - const GameRecord({ - required this.uuid, - required this.ts, - }); -} diff --git a/lib/game/entity/timer.dart b/lib/game/entity/timer.dart deleted file mode 100644 index f3f37c5a9..000000000 --- a/lib/game/entity/timer.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:pausable_timer/pausable_timer.dart'; - -class GameTimer extends StateNotifier { - PausableTimer? _timer; - - bool get timerStart => _timer != null; - - GameTimer({ - Duration initial = Duration.zero, - }) : super(initial); - - void startTimer() { - assert(_timer == null, "Timer already started"); - _timer ??= PausableTimer.periodic(const Duration(milliseconds: 1000), () { - state = state + const Duration(milliseconds: 1000); - }) - ..start(); - } - - @override - Duration get state => super.state; - - @override - set state(Duration state) => super.state = state; - - void reset() { - _timer?.reset(); - state = Duration.zero; - } - - void pause() { - _timer?.pause(); - } - - void resume() { - assert(_timer?.isPaused != false, "Timer not yet paused"); - _timer?.start(); - } - - void stopTimer() { - _timer?.cancel(); - _timer = null; - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } -} diff --git a/lib/game/i18n.dart b/lib/game/i18n.dart deleted file mode 100644 index ffaff285d..000000000 --- a/lib/game/i18n.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:mimir/l10n/common.dart'; - -const i18n = _I18n(); -const _ns = "game"; - -class _I18n with CommonI18nMixin, CommonGameI18nMixin { - const _I18n(); - - final settings = const _Settings(); - - String get navigation => "$_ns.navigation".tr(); - - String get continueGame => "$_ns.continueGame".tr(); - - String get loadGame => "$_ns.loadGame".tr(); - - String get noGameRecords => "$_ns.noGameRecords".tr(); - - String loadGameFromQrCode(String gameName) => "$_ns.loadGameFromQrCode".tr(args: [ - "game.$gameName.title".tr(), - ]); -} - -mixin class CommonGameI18nMixin { - String get gameMode => "$_ns.gameMode".tr(); - - String get tryAgain => "$_ns.tryAgain".tr(); - - String get newGame => "$_ns.newGame".tr(); - - String get youWin => "$_ns.youWin".tr(); - - String get gameOver => "$_ns.gameOver".tr(); - - String get changeGameModeRequest => "$_ns.changeGameModeRequest".tr(); - - String changeGameModeAction(String newMode) => "$_ns.changeGameModeAction".tr(args: [newMode]); - - String formatPlaytime(Duration duration) { - final min = duration.inMinutes.toString(); - final sec = duration.inSeconds.remainder(60).toString(); - return '$min:${sec.padLeft(2, "0")}'; - } -} - -class _Settings { - static const ns = "$_ns.settings"; - - const _Settings(); - - String get enableHapticFeedback => "$ns.enableHapticFeedback.title".tr(); - - String get enableHapticFeedbackDesc => "$ns.enableHapticFeedback.desc".tr(); - - String get showGameNavigation => "$ns.showGameNavigation.title".tr(); - - String get showGameNavigationDesc => "$ns.showGameNavigation.desc".tr(); -} diff --git a/lib/game/index.dart b/lib/game/index.dart deleted file mode 100644 index 6912afdab..000000000 --- a/lib/game/index.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mimir/r.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/game/2048/card.dart'; -import 'package:mimir/game/minesweeper/card.dart'; -import 'package:mimir/game/sudoku/card.dart'; - -import "i18n.dart"; - -class GamePage extends ConsumerStatefulWidget { - const GamePage({super.key}); - - @override - ConsumerState createState() => _GamePageState(); -} - -class _GamePageState extends ConsumerState { - @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, bool innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: SliverAppBar( - floating: true, - snap: true, - title: i18n.navigation.text(), - forceElevated: innerBoxIsScrolled, - ), - ), - ]; - }, - body: CustomScrollView( - slivers: [ - SliverList.list( - children: [ - const GameAppCard2048(), - const GameAppCardMinesweeper(), - const GameAppCardSudoku(), - if (context.locale == R.zhHansLocale) - const Opacity( - opacity: 0.75, - child: CnBureauAdvice4Game(), - ).padSymmetric(h: 8, v: 12), - ], - ), - ], - ), - ), - ); - } -} - -class CnBureauAdvice4Game extends StatelessWidget { - const CnBureauAdvice4Game({super.key}); - - @override - Widget build(BuildContext context) { - Widget title(String text) => text.text(textAlign: TextAlign.center); - Widget sub(String text) => text.text(style: context.textTheme.labelMedium, textAlign: TextAlign.center); - return [ - title("健康游戏忠告"), - sub("抵制不良游戏,拒绝盗版游戏。"), - sub("注意自我保护,谨防受骗上当。"), - sub("适度游戏益脑,沉迷游戏伤身。"), - sub("合理安排时间,享受健康生活。"), - ].column(); - } -} diff --git a/lib/game/minesweeper/card.dart b/lib/game/minesweeper/card.dart deleted file mode 100644 index 2106d74fc..000000000 --- a/lib/game/minesweeper/card.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/adaptive/dialog.dart'; -import 'package:mimir/game/widget/mode.dart'; -import 'package:mimir/game/widget/card.dart'; - -import 'entity/mode.dart'; -import 'entity/pref.dart'; -import 'settings.dart'; -import 'i18n.dart'; -import 'storage.dart'; - -class GameAppCardMinesweeper extends ConsumerStatefulWidget { - const GameAppCardMinesweeper({super.key}); - - @override - ConsumerState createState() => _GameAppCardMinesweeperState(); -} - -class _GameAppCardMinesweeperState extends ConsumerState { - @override - Widget build(BuildContext context) { - return OfflineGameAppCard( - name: i18n.title, - baseRoute: "/minesweeper", - save: StorageMinesweeper.save, - supportRecords: true, - view: buildGameModeCard().align(at: Alignment.centerLeft), - ); - } - - Widget buildGameModeCard() { - final pref = ref.watch(SettingsMinesweeper.$.$pref); - return GameModeSelectorCard( - all: GameModeMinesweeper.values, - current: pref.mode, - onChanged: (newMode) async { - if (StorageMinesweeper.save.exists()) { - final confirm = await context.showActionRequest( - desc: i18n.changeGameModeRequest, - action: i18n.changeGameModeAction(newMode.l10n()), - cancel: i18n.cancel, - ); - if (confirm != true) return; - } - ref.read(SettingsMinesweeper.$.$pref.notifier).set(pref.copyWith( - mode: newMode, - )); - StorageMinesweeper.save.delete(); - }, - ); - } -} diff --git a/lib/game/minesweeper/entity/blueprint.dart b/lib/game/minesweeper/entity/blueprint.dart deleted file mode 100644 index 920a6d228..000000000 --- a/lib/game/minesweeper/entity/blueprint.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:mimir/game/entity/blueprint.dart'; -import 'package:mimir/intent/deep_link/utils.dart'; -import 'package:mimir/utils/byte_io/reader.dart'; -import 'package:mimir/utils/byte_io/writer.dart'; - -import 'board.dart'; -import 'state.dart'; -import 'mode.dart'; - -@immutable -class BlueprintMinesweeper implements GameBlueprint { - final ({int row, int column}) firstClick; - final CellBoardBuilder builder; - final GameModeMinesweeper mode; - - const BlueprintMinesweeper({ - required this.firstClick, - required this.builder, - required this.mode, - }); - - factory BlueprintMinesweeper.from(String data) { - final bytes = decodeBytesFromUrl(data); - final reader = ByteReader(bytes); - final modeRaw = reader.strUtf8(); - final firstClick = ( - row: reader.uint8(), - column: reader.uint8(), - ); - final mode = GameModeMinesweeper.fromJson(modeRaw); - final builder = CellBoardBuilder.readBlueprint(reader); - return BlueprintMinesweeper( - firstClick: firstClick, - builder: builder, - mode: mode, - ); - } - - @override - String build() { - final writer = ByteWriter(512); - writer.strUtf8(mode.toJson()); - writer.uint8(firstClick.row); - writer.uint8(firstClick.column); - builder.writeBlueprint(writer); - final bytes = writer.build(); - return encodeBytesForUrl(bytes); - } - - @override - GameStateMinesweeper create() { - builder.updateCells(); - return GameStateMinesweeper( - mode: mode, - board: builder.build(), - firstClick: firstClick, - ); - } -} diff --git a/lib/game/minesweeper/entity/board.dart b/lib/game/minesweeper/entity/board.dart deleted file mode 100644 index b007e3b21..000000000 --- a/lib/game/minesweeper/entity/board.dart +++ /dev/null @@ -1,355 +0,0 @@ -import 'package:copy_with_extension/copy_with_extension.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/utils/byte_io/byte_io.dart'; -import 'package:mimir/utils/list2d/list2d.dart'; -import 'dart:math'; -import 'save.dart'; -import '../utils.dart'; -import 'cell.dart'; - -part "board.g.dart"; - -typedef MinesweeperBoardGenerator = void Function( - CellBoardBuilder board, { - required int mines, - required int rowFirstClick, - required int columnFirstClick, -}); - -abstract class ICellBoard { - int get rows; - - int get columns; - - List2D get cells; - - const ICellBoard(); - - int indexOf({required int row, required int column}) { - return row * columns + column; - } - - TCell getCell({required int row, required int column}) { - return cells.get(row, column); - } - - Iterable iterateAround({required int row, required int column}) sync* { - for (final (dx, dy) in CellBoard.nearbyDelta) { - final nearbyRow = row + dx; - final nearbyCol = column + dy; - if (inRange(row: nearbyRow, col: nearbyCol)) { - yield getCell(row: nearbyRow, column: nearbyCol); - } - } - } - - Iterable iterateAllCells() sync* { - for (var row = 0; row < rows; row++) { - for (var column = 0; column < columns; column++) { - yield getCell(row: row, column: column); - } - } - } - - bool inRange({required int row, required int col}) { - return 0 <= row && row < rows && 0 <= col && col < columns; - } - - int countAllByState({required state}) { - var count = 0; - for (final cell in iterateAllCells()) { - if (cell.state == state) { - count += 1; - } - } - return count; - } - - int countAroundByState({required Cell cell, required state}) { - var count = 0; - for (final cell in iterateAround(row: cell.row, column: cell.column)) { - if (cell.state == state) { - count += 1; - } - } - return count; - } - - int countAroundMines({required Cell cell}) { - var count = 0; - for (final cell in iterateAround(row: cell.row, column: cell.column)) { - if (cell.mine) { - count += 1; - } - } - return count; - } -} - -@JsonSerializable() -@CopyWith(skipFields: true) -class CellBoard extends ICellBoard { - static const nearbyDelta = [(-1, 1), (0, 1), (1, 1), (-1, 0), /*(0,0)*/ (1, 0), (-1, -1), (0, -1), (1, -1)]; - static const nearbyDeltaAndThis = [...nearbyDelta, (0, 0)]; - final int mines; - @override - final List2D cells; - - @override - int get rows => cells.rows; - - @override - int get columns => cells.columns; - - const CellBoard({ - required this.mines, - required this.cells, - }); - - const CellBoard.byDefault() - : mines = 0, - cells = const List2D(); - - factory CellBoard.empty({ - required int rows, - required int columns, - }) { - final builder = CellBoardBuilder.generate(rows: rows, columns: columns); - return builder.build(); - } - - factory CellBoard.withMines({ - required int rows, - required int columns, - required int mines, - required ({int row, int column}) firstClick, - }) { - final builder = CellBoardBuilder.generate(rows: rows, columns: columns); - builder.randomMines(mines: mines, firstClick: firstClick); - return builder.build(); - } - - factory CellBoard.fromSave(SaveMinesweeper save) { - final cells = save.cells.mapIndexed( - (row, column, index, cell) => CellBuilder(row: row, column: column) - ..mine = cell.mine - ..state = cell.state, - ); - final builder = CellBoardBuilder( - cells: cells, - ); - builder.updateCells(); - return builder.build(); - } - - List2D toSave() { - return cells.map((cell) => Cell4Save(mine: cell.mine, state: cell.state)); - } - - CellBoard changeCell({required row, required column, required state}) { - final newCells = List2D.of(cells); - newCells.set(row, column, cells.get(row, column).copyWith(state: state)); - return copyWith(cells: newCells); - } - - Map toJson() => _$CellBoardToJson(this); - - factory CellBoard.fromJson(Map json) => _$CellBoardFromJson(json); -} - -extension CellBoardX on CellBoard { - CellBoardBuilder toBuilder() { - final builder = CellBoardBuilder( - cells: cells.map((cell) => CellBuilder( - row: cell.row, - column: cell.column, - mine: cell.mine, - ))); - builder.updateCells(); - return builder; - } -} - -class CellBuilder implements Cell { - @override - final int row; - @override - final int column; - @override - bool mine; - @override - var state = CellState.covered; - @override - var minesAround = 0; - - CellBuilder({ - required this.row, - required this.column, - this.mine = false, - }); - - Cell build() { - return Cell( - row: row, - column: column, - minesAround: minesAround, - state: state, - mine: mine, - ); - } - - @override - Map toJson() { - throw UnimplementedError(); - } - - void writeBlueprint(ByteWriter writer) { - writer.uint8(row); - writer.uint8(column); - writer.b(mine); - } - - static CellBuilder readBlueprint(ByteReader reader) { - return CellBuilder( - row: reader.uint8(), - column: reader.uint8(), - mine: reader.b(), - ); - } -} - -class CellBoardBuilder extends ICellBoard { - @override - late final List2D cells; - int mines = 0; - - @override - int get rows => cells.rows; - - @override - int get columns => cells.columns; - - CellBoardBuilder.generate({ - required int rows, - required int columns, - }) : assert(rows > 0), - assert(columns > 0), - cells = List2D.generate( - rows, - columns, - (row, column, index) => CellBuilder( - row: row, - column: column, - ), - ); - - CellBoardBuilder({ - required this.cells, - }); - - void updateCells() { - for (final cell in cells) { - cell.minesAround = 0; - } - for (final cell in cells) { - if (cell.mine) { - _addRoundCellMineNum(row: cell.row, column: cell.column); - } - } - _countMines(); - } - - void _addRoundCellMineNum({required row, required column}) { - for (final neighbor in iterateAround(row: row, column: column)) { - neighbor.minesAround += 1; - } - } - - void _countMines() { - mines = cells.where((cell) => cell.mine).length; - } - - void generate({ - MinesweeperBoardGenerator generator = randomGenerateMines, - required int mines, - required int rowFirstClick, - required int columnFirstClick, - }) { - generator( - this, - mines: mines, - rowFirstClick: rowFirstClick, - columnFirstClick: columnFirstClick, - ); - } - - void randomMines({ - required int mines, - required ({int row, int column}) firstClick, - }) { - final rand = Random(); - final candidates = List.generate(rows * columns, (index) => (row: index ~/ columns, column: index % columns)); - // Clicked cell and one-cell nearby cells can't be mines. - for (final (dx, dy) in CellBoard.nearbyDeltaAndThis) { - final row = firstClick.row + dx; - final column = firstClick.column + dy; - candidates.remove((row: row, column: column)); - } - final maxMines = candidates.length - 1; - assert(mines <= maxMines, "The max mine is $maxMines, but $mines is given."); - var remaining = min(mines, maxMines); - this.mines = remaining; - while (candidates.isNotEmpty && remaining > 0) { - final index = rand.nextInt(candidates.length); - final (:row, :column) = candidates[index]; - final cell = getCell(row: row, column: column); - if (!cell.mine) { - cell.mine = true; - // count as mine created - for (final neighbor in iterateAround(row: row, column: column)) { - neighbor.minesAround += 1; - } - remaining--; - candidates.removeAt(index); - } - } - } - - CellBoard build() { - return CellBoard( - mines: mines, - cells: cells.map((e) => e.build()), - ); - } - - void writeBlueprint(ByteWriter writer) { - writer.uint8(rows); - writer.uint8(columns); - final mineCells = cells.where((cell) => cell.mine).toList(); - writer.uint8(mineCells.length); - for (final mineCell in mineCells) { - mineCell.writeBlueprint(writer); - } - } - - static CellBoardBuilder readBlueprint(ByteReader reader) { - final rows = reader.uint8(); - final columns = reader.uint8(); - final len = reader.uint8(); - final mineCells = []; - for (var i = 0; i < len; i++) { - mineCells.add(CellBuilder.readBlueprint(reader)); - } - final cells = List2D.generate( - rows, - columns, - (row, column, index) => CellBuilder(row: row, column: column), - ); - for (final mineCell in mineCells) { - cells.set(mineCell.row, mineCell.column, mineCell); - } - return CellBoardBuilder( - cells: cells, - ); - } -} diff --git a/lib/game/minesweeper/entity/board.g.dart b/lib/game/minesweeper/entity/board.g.dart deleted file mode 100644 index 2fad6ff6d..000000000 --- a/lib/game/minesweeper/entity/board.g.dart +++ /dev/null @@ -1,74 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'board.dart'; - -// ************************************************************************** -// CopyWithGenerator -// ************************************************************************** - -abstract class _$CellBoardCWProxy { - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// CellBoard(...).copyWith(id: 12, name: "My name") - /// ```` - CellBoard call({ - int? mines, - List2D? cells, - }); -} - -/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfCellBoard.copyWith(...)`. -class _$CellBoardCWProxyImpl implements _$CellBoardCWProxy { - const _$CellBoardCWProxyImpl(this._value); - - final CellBoard _value; - - @override - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// CellBoard(...).copyWith(id: 12, name: "My name") - /// ```` - CellBoard call({ - Object? mines = const $CopyWithPlaceholder(), - Object? cells = const $CopyWithPlaceholder(), - }) { - return CellBoard( - mines: mines == const $CopyWithPlaceholder() || mines == null - ? _value.mines - // ignore: cast_nullable_to_non_nullable - : mines as int, - cells: cells == const $CopyWithPlaceholder() || cells == null - ? _value.cells - // ignore: cast_nullable_to_non_nullable - : cells as List2D, - ); - } -} - -extension $CellBoardCopyWith on CellBoard { - /// Returns a callable class that can be used as follows: `instanceOfCellBoard.copyWith(...)`. - // ignore: library_private_types_in_public_api - _$CellBoardCWProxy get copyWith => _$CellBoardCWProxyImpl(this); -} - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -CellBoard _$CellBoardFromJson(Map json) => CellBoard( - mines: (json['mines'] as num).toInt(), - cells: List2D.fromJson( - json['cells'] as Map, (value) => Cell.fromJson(value as Map)), - ); - -Map _$CellBoardToJson(CellBoard instance) => { - 'mines': instance.mines, - 'cells': instance.cells.toJson( - (value) => value, - ), - }; diff --git a/lib/game/minesweeper/entity/cell.dart b/lib/game/minesweeper/entity/cell.dart deleted file mode 100644 index a06b87749..000000000 --- a/lib/game/minesweeper/entity/cell.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:copy_with_extension/copy_with_extension.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part "cell.g.dart"; - -@JsonEnum() -enum CellState { - @JsonValue(0) - covered(showCover: true, showFlag: false), - @JsonValue(1) - blank(showCover: false, showFlag: false), - @JsonValue(2) - flag(showCover: true, showFlag: true); - - final bool showCover; - final bool showFlag; - - const CellState({ - required this.showCover, - required this.showFlag, - }); -} - -@JsonSerializable() -@CopyWith() -class Cell { - final int row; - final int column; - final bool mine; - final CellState state; - final int minesAround; - - const Cell({ - required this.row, - required this.column, - this.mine = false, - this.state = CellState.covered, - required this.minesAround, - }); - - Map toJson() => _$CellToJson(this); - - factory Cell.fromJson(Map json) => _$CellFromJson(json); - - @override - String toString() { - return "[$row,$column] ${mine ? "X" : ""} $state with $minesAround mines"; - } -} diff --git a/lib/game/minesweeper/entity/cell.g.dart b/lib/game/minesweeper/entity/cell.g.dart deleted file mode 100644 index 94567c12f..000000000 --- a/lib/game/minesweeper/entity/cell.g.dart +++ /dev/null @@ -1,126 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'cell.dart'; - -// ************************************************************************** -// CopyWithGenerator -// ************************************************************************** - -abstract class _$CellCWProxy { - Cell row(int row); - - Cell column(int column); - - Cell mine(bool mine); - - Cell state(CellState state); - - Cell minesAround(int minesAround); - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `Cell(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. - /// - /// Usage - /// ```dart - /// Cell(...).copyWith(id: 12, name: "My name") - /// ```` - Cell call({ - int? row, - int? column, - bool? mine, - CellState? state, - int? minesAround, - }); -} - -/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfCell.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfCell.copyWith.fieldName(...)` -class _$CellCWProxyImpl implements _$CellCWProxy { - const _$CellCWProxyImpl(this._value); - - final Cell _value; - - @override - Cell row(int row) => this(row: row); - - @override - Cell column(int column) => this(column: column); - - @override - Cell mine(bool mine) => this(mine: mine); - - @override - Cell state(CellState state) => this(state: state); - - @override - Cell minesAround(int minesAround) => this(minesAround: minesAround); - - @override - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `Cell(...).copyWith.fieldName(...)` to override fields one at a time with nullification support. - /// - /// Usage - /// ```dart - /// Cell(...).copyWith(id: 12, name: "My name") - /// ```` - Cell call({ - Object? row = const $CopyWithPlaceholder(), - Object? column = const $CopyWithPlaceholder(), - Object? mine = const $CopyWithPlaceholder(), - Object? state = const $CopyWithPlaceholder(), - Object? minesAround = const $CopyWithPlaceholder(), - }) { - return Cell( - row: row == const $CopyWithPlaceholder() || row == null - ? _value.row - // ignore: cast_nullable_to_non_nullable - : row as int, - column: column == const $CopyWithPlaceholder() || column == null - ? _value.column - // ignore: cast_nullable_to_non_nullable - : column as int, - mine: mine == const $CopyWithPlaceholder() || mine == null - ? _value.mine - // ignore: cast_nullable_to_non_nullable - : mine as bool, - state: state == const $CopyWithPlaceholder() || state == null - ? _value.state - // ignore: cast_nullable_to_non_nullable - : state as CellState, - minesAround: minesAround == const $CopyWithPlaceholder() || minesAround == null - ? _value.minesAround - // ignore: cast_nullable_to_non_nullable - : minesAround as int, - ); - } -} - -extension $CellCopyWith on Cell { - /// Returns a callable class that can be used as follows: `instanceOfCell.copyWith(...)` or like so:`instanceOfCell.copyWith.fieldName(...)`. - // ignore: library_private_types_in_public_api - _$CellCWProxy get copyWith => _$CellCWProxyImpl(this); -} - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Cell _$CellFromJson(Map json) => Cell( - row: (json['row'] as num).toInt(), - column: (json['column'] as num).toInt(), - mine: json['mine'] as bool? ?? false, - state: $enumDecodeNullable(_$CellStateEnumMap, json['state']) ?? CellState.covered, - minesAround: (json['minesAround'] as num).toInt(), - ); - -Map _$CellToJson(Cell instance) => { - 'row': instance.row, - 'column': instance.column, - 'mine': instance.mine, - 'state': _$CellStateEnumMap[instance.state]!, - 'minesAround': instance.minesAround, - }; - -const _$CellStateEnumMap = { - CellState.covered: 0, - CellState.blank: 1, - CellState.flag: 2, -}; diff --git a/lib/game/minesweeper/entity/mode.dart b/lib/game/minesweeper/entity/mode.dart deleted file mode 100644 index 3f1fcfc9f..000000000 --- a/lib/game/minesweeper/entity/mode.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/game/entity/game_mode.dart'; - -@JsonSerializable(createToJson: false, createFactory: false) -class GameModeMinesweeper extends GameMode { - final int gameRows; - final int gameColumns; - final int gameMines; - static const defaultRows = 16; - static const defaultColumns = 9; - static const easy = GameModeMinesweeper._( - name: "easy", - gameRows: defaultRows, - gameColumns: defaultColumns, - gameMines: (defaultRows * defaultColumns * 0.123 + 0.5) ~/ 1, - ); - static const normal = GameModeMinesweeper._( - name: "normal", - gameRows: defaultRows, - gameColumns: defaultColumns, - gameMines: (defaultRows * defaultColumns * 0.15 + 0.5) ~/ 1, - ); - static const hard = GameModeMinesweeper._( - name: "hard", - gameRows: defaultRows, - gameColumns: defaultColumns, - gameMines: (defaultRows * defaultColumns * 0.2 + 0.5) ~/ 1, - ); - - static final name2mode = { - "easy": easy, - "normal": normal, - "hard": hard, - }; - - static final values = [ - easy, - normal, - hard, - ]; - - const GameModeMinesweeper._({ - required super.name, - required this.gameRows, - required this.gameColumns, - required this.gameMines, - }); - - factory GameModeMinesweeper.fromJson(String name) => name2mode[name] ?? easy; - - @override - bool operator ==(Object other) { - return other is GameModeMinesweeper && - runtimeType == other.runtimeType && - name == other.name && - gameRows == other.gameRows && - gameColumns == other.gameColumns && - gameMines == other.gameMines; - } - - @override - int get hashCode => Object.hash(name, gameRows, gameColumns, gameMines); - - @override - String l10n() => "game.minesweeper.gameMode.$name".tr(); -} diff --git a/lib/game/minesweeper/entity/pref.dart b/lib/game/minesweeper/entity/pref.dart deleted file mode 100644 index 5877e8e37..000000000 --- a/lib/game/minesweeper/entity/pref.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:copy_with_extension/copy_with_extension.dart'; -import 'package:json_annotation/json_annotation.dart'; - -import 'mode.dart'; - -part "pref.g.dart"; - -@JsonSerializable() -@CopyWith(skipFields: true) -class GamePrefMinesweeper { - final GameModeMinesweeper mode; - - const GamePrefMinesweeper({ - this.mode = GameModeMinesweeper.easy, - }); - - @override - bool operator ==(Object other) { - return other is GamePrefMinesweeper && runtimeType == other.runtimeType && mode == other.mode; - } - - @override - int get hashCode => Object.hash(mode, 0); - - factory GamePrefMinesweeper.fromJson(Map json) => _$GamePrefMinesweeperFromJson(json); - - Map toJson() => _$GamePrefMinesweeperToJson(this); -} diff --git a/lib/game/minesweeper/entity/pref.g.dart b/lib/game/minesweeper/entity/pref.g.dart deleted file mode 100644 index 10275db1d..000000000 --- a/lib/game/minesweeper/entity/pref.g.dart +++ /dev/null @@ -1,63 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'pref.dart'; - -// ************************************************************************** -// CopyWithGenerator -// ************************************************************************** - -abstract class _$GamePrefMinesweeperCWProxy { - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// GamePrefMinesweeper(...).copyWith(id: 12, name: "My name") - /// ```` - GamePrefMinesweeper call({ - GameModeMinesweeper? mode, - }); -} - -/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfGamePrefMinesweeper.copyWith(...)`. -class _$GamePrefMinesweeperCWProxyImpl implements _$GamePrefMinesweeperCWProxy { - const _$GamePrefMinesweeperCWProxyImpl(this._value); - - final GamePrefMinesweeper _value; - - @override - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// GamePrefMinesweeper(...).copyWith(id: 12, name: "My name") - /// ```` - GamePrefMinesweeper call({ - Object? mode = const $CopyWithPlaceholder(), - }) { - return GamePrefMinesweeper( - mode: mode == const $CopyWithPlaceholder() || mode == null - ? _value.mode - // ignore: cast_nullable_to_non_nullable - : mode as GameModeMinesweeper, - ); - } -} - -extension $GamePrefMinesweeperCopyWith on GamePrefMinesweeper { - /// Returns a callable class that can be used as follows: `instanceOfGamePrefMinesweeper.copyWith(...)`. - // ignore: library_private_types_in_public_api - _$GamePrefMinesweeperCWProxy get copyWith => _$GamePrefMinesweeperCWProxyImpl(this); -} - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -GamePrefMinesweeper _$GamePrefMinesweeperFromJson(Map json) => GamePrefMinesweeper( - mode: json['mode'] == null ? GameModeMinesweeper.easy : GameModeMinesweeper.fromJson(json['mode'] as String), - ); - -Map _$GamePrefMinesweeperToJson(GamePrefMinesweeper instance) => { - 'mode': instance.mode, - }; diff --git a/lib/game/minesweeper/entity/record.dart b/lib/game/minesweeper/entity/record.dart deleted file mode 100644 index e2b747ead..000000000 --- a/lib/game/minesweeper/entity/record.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; -import 'package:mimir/game/entity/game_result.dart'; -import 'package:mimir/game/entity/record.dart'; -import 'package:uuid/uuid.dart'; - -import 'blueprint.dart'; -import 'board.dart'; -import 'mode.dart'; - -part "record.g.dart"; - -@JsonSerializable() -@immutable -class RecordMinesweeper extends GameRecord { - final GameResult result; - final int rows; - final int columns; - final int mines; - final Duration playtime; - final GameModeMinesweeper mode; - final String blueprint; - - const RecordMinesweeper({ - required super.uuid, - required super.ts, - required this.result, - required this.rows, - required this.columns, - required this.mines, - required this.playtime, - required this.mode, - required this.blueprint, - }); - - factory RecordMinesweeper.createFrom({ - required CellBoard board, - required Duration playtime, - required GameModeMinesweeper mode, - required GameResult result, - required ({int row, int column}) firstClick, - }) { - final blueprint = BlueprintMinesweeper( - firstClick: firstClick, - builder: board.toBuilder(), - mode: mode, - ); - return RecordMinesweeper( - uuid: const Uuid().v4(), - ts: DateTime.now(), - result: result, - rows: board.rows, - columns: board.columns, - mines: board.mines, - playtime: playtime, - mode: mode, - blueprint: blueprint.build(), - ); - } - - Map toJson() => _$RecordMinesweeperToJson(this); - - factory RecordMinesweeper.fromJson(Map json) => _$RecordMinesweeperFromJson(json); -} diff --git a/lib/game/minesweeper/entity/record.g.dart b/lib/game/minesweeper/entity/record.g.dart deleted file mode 100644 index b8837f3cd..000000000 --- a/lib/game/minesweeper/entity/record.g.dart +++ /dev/null @@ -1,36 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'record.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -RecordMinesweeper _$RecordMinesweeperFromJson(Map json) => RecordMinesweeper( - uuid: json['uuid'] as String? ?? genUuidV4(), - ts: DateTime.parse(json['ts'] as String), - result: $enumDecode(_$GameResultEnumMap, json['result']), - rows: (json['rows'] as num).toInt(), - columns: (json['columns'] as num).toInt(), - mines: (json['mines'] as num).toInt(), - playtime: Duration(microseconds: (json['playtime'] as num).toInt()), - mode: GameModeMinesweeper.fromJson(json['mode'] as String), - blueprint: json['blueprint'] as String, - ); - -Map _$RecordMinesweeperToJson(RecordMinesweeper instance) => { - 'uuid': instance.uuid, - 'ts': instance.ts.toIso8601String(), - 'result': _$GameResultEnumMap[instance.result]!, - 'rows': instance.rows, - 'columns': instance.columns, - 'mines': instance.mines, - 'playtime': instance.playtime.inMicroseconds, - 'mode': instance.mode, - 'blueprint': instance.blueprint, - }; - -const _$GameResultEnumMap = { - GameResult.victory: 'victory', - GameResult.gameOver: 'gameOver', -}; diff --git a/lib/game/minesweeper/entity/save.dart b/lib/game/minesweeper/entity/save.dart deleted file mode 100644 index e90df9fd4..000000000 --- a/lib/game/minesweeper/entity/save.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/utils/list2d/list2d.dart'; - -import 'mode.dart'; -import 'cell.dart'; - -part "save.g.dart"; - -List2D _defaultCells() { - return List2D.generate( - GameModeMinesweeper.defaultRows, - GameModeMinesweeper.defaultColumns, - (row, column, index) => const Cell4Save(), - ); -} - -@JsonSerializable() -class Cell4Save { - final bool mine; - final CellState state; - - const Cell4Save({ - this.mine = false, - this.state = CellState.covered, - }); - - Map toJson() => _$Cell4SaveToJson(this); - - factory Cell4Save.fromJson(Map json) => _$Cell4SaveFromJson(json); -} - -@JsonSerializable() -class SaveMinesweeper { - @JsonKey(defaultValue: _defaultCells) - final List2D cells; - final Duration playtime; - final GameModeMinesweeper mode; - final ({int row, int column}) firstClick; - - const SaveMinesweeper({ - required this.cells, - this.playtime = Duration.zero, - this.mode = GameModeMinesweeper.easy, - required this.firstClick, - }); - - Map toJson() => _$SaveMinesweeperToJson(this); - - factory SaveMinesweeper.fromJson(Map json) => _$SaveMinesweeperFromJson(json); -} diff --git a/lib/game/minesweeper/entity/save.g.dart b/lib/game/minesweeper/entity/save.g.dart deleted file mode 100644 index e44c824ce..000000000 --- a/lib/game/minesweeper/entity/save.g.dart +++ /dev/null @@ -1,57 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'save.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Cell4Save _$Cell4SaveFromJson(Map json) => Cell4Save( - mine: json['mine'] as bool? ?? false, - state: $enumDecodeNullable(_$CellStateEnumMap, json['state']) ?? CellState.covered, - ); - -Map _$Cell4SaveToJson(Cell4Save instance) => { - 'mine': instance.mine, - 'state': _$CellStateEnumMap[instance.state]!, - }; - -const _$CellStateEnumMap = { - CellState.covered: 0, - CellState.blank: 1, - CellState.flag: 2, -}; - -SaveMinesweeper _$SaveMinesweeperFromJson(Map json) => SaveMinesweeper( - cells: json['cells'] == null - ? _defaultCells() - : List2D.fromJson( - json['cells'] as Map, (value) => Cell4Save.fromJson(value as Map)), - playtime: json['playtime'] == null ? Duration.zero : Duration(microseconds: (json['playtime'] as num).toInt()), - mode: json['mode'] == null ? GameModeMinesweeper.easy : GameModeMinesweeper.fromJson(json['mode'] as String), - firstClick: _$recordConvert( - json['firstClick'], - ($jsonValue) => ( - column: ($jsonValue['column'] as num).toInt(), - row: ($jsonValue['row'] as num).toInt(), - ), - ), - ); - -Map _$SaveMinesweeperToJson(SaveMinesweeper instance) => { - 'cells': instance.cells.toJson( - (value) => value, - ), - 'playtime': instance.playtime.inMicroseconds, - 'mode': instance.mode, - 'firstClick': { - 'column': instance.firstClick.column, - 'row': instance.firstClick.row, - }, - }; - -$Rec _$recordConvert<$Rec>( - Object? value, - $Rec Function(Map) convert, -) => - convert(value as Map); diff --git a/lib/game/minesweeper/entity/screen.dart b/lib/game/minesweeper/entity/screen.dart deleted file mode 100644 index 845c93f0a..000000000 --- a/lib/game/minesweeper/entity/screen.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:ui'; - -class Screen { - final double width; - final double height; - final int gameRows; - final int gameColumns; - - const Screen({ - required this.width, - required this.height, - required this.gameRows, - required this.gameColumns, - }); - - double getCellWidth() { - var wCell = (width / (gameColumns + 1)).floorToDouble(); - var hCell = (height / (gameRows + 3)).floorToDouble(); - var cellWidth = wCell > hCell ? hCell : wCell; - return cellWidth; - } - - Size getBoardSize() { - final width = getCellWidth() * gameColumns; - final height = getCellWidth() * gameRows; - final boardSize = Size(width, height); - return boardSize; - } -} diff --git a/lib/game/minesweeper/entity/state.dart b/lib/game/minesweeper/entity/state.dart deleted file mode 100644 index 4d4e5b3e4..000000000 --- a/lib/game/minesweeper/entity/state.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:copy_with_extension/copy_with_extension.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/game/entity/game_status.dart'; - -import 'save.dart'; -import 'board.dart'; -import 'mode.dart'; - -part "state.g.dart"; - -@JsonSerializable() -@CopyWith(skipFields: true) -@immutable -class GameStateMinesweeper { - final GameStatus status; - final GameModeMinesweeper mode; - final CellBoard board; - final Duration playtime; - final ({int row, int column})? firstClick; - - const GameStateMinesweeper({ - this.status = GameStatus.idle, - required this.mode, - required this.board, - this.firstClick, - this.playtime = Duration.zero, - }); - - GameStateMinesweeper.byDefault() - : status = GameStatus.idle, - mode = GameModeMinesweeper.easy, - playtime = Duration.zero, - firstClick = null, - board = CellBoard.empty(rows: GameModeMinesweeper.easy.gameRows, columns: GameModeMinesweeper.easy.gameColumns); - - int get rows => board.rows; - - int get columns => board.columns; - - Map toJson() => _$GameStateMinesweeperToJson(this); - - factory GameStateMinesweeper.fromJson(Map json) => _$GameStateMinesweeperFromJson(json); - - factory GameStateMinesweeper.fromSave(SaveMinesweeper save) { - final board = CellBoard.fromSave(save); - return GameStateMinesweeper( - mode: save.mode, - board: board, - playtime: save.playtime, - status: GameStatus.running, - firstClick: save.firstClick, - ); - } - - SaveMinesweeper toSave() { - assert(firstClick != null, "save before first click"); - return SaveMinesweeper( - cells: board.toSave(), - playtime: playtime, - mode: mode, - firstClick: firstClick!, - ); - } -} diff --git a/lib/game/minesweeper/entity/state.g.dart b/lib/game/minesweeper/entity/state.g.dart deleted file mode 100644 index 6a41fe5a7..000000000 --- a/lib/game/minesweeper/entity/state.g.dart +++ /dev/null @@ -1,119 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'state.dart'; - -// ************************************************************************** -// CopyWithGenerator -// ************************************************************************** - -abstract class _$GameStateMinesweeperCWProxy { - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// GameStateMinesweeper(...).copyWith(id: 12, name: "My name") - /// ```` - GameStateMinesweeper call({ - GameStatus? status, - GameModeMinesweeper? mode, - CellBoard? board, - ({int column, int row})? firstClick, - Duration? playtime, - }); -} - -/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfGameStateMinesweeper.copyWith(...)`. -class _$GameStateMinesweeperCWProxyImpl implements _$GameStateMinesweeperCWProxy { - const _$GameStateMinesweeperCWProxyImpl(this._value); - - final GameStateMinesweeper _value; - - @override - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// GameStateMinesweeper(...).copyWith(id: 12, name: "My name") - /// ```` - GameStateMinesweeper call({ - Object? status = const $CopyWithPlaceholder(), - Object? mode = const $CopyWithPlaceholder(), - Object? board = const $CopyWithPlaceholder(), - Object? firstClick = const $CopyWithPlaceholder(), - Object? playtime = const $CopyWithPlaceholder(), - }) { - return GameStateMinesweeper( - status: status == const $CopyWithPlaceholder() || status == null - ? _value.status - // ignore: cast_nullable_to_non_nullable - : status as GameStatus, - mode: mode == const $CopyWithPlaceholder() || mode == null - ? _value.mode - // ignore: cast_nullable_to_non_nullable - : mode as GameModeMinesweeper, - board: board == const $CopyWithPlaceholder() || board == null - ? _value.board - // ignore: cast_nullable_to_non_nullable - : board as CellBoard, - firstClick: firstClick == const $CopyWithPlaceholder() - ? _value.firstClick - // ignore: cast_nullable_to_non_nullable - : firstClick as ({int column, int row})?, - playtime: playtime == const $CopyWithPlaceholder() || playtime == null - ? _value.playtime - // ignore: cast_nullable_to_non_nullable - : playtime as Duration, - ); - } -} - -extension $GameStateMinesweeperCopyWith on GameStateMinesweeper { - /// Returns a callable class that can be used as follows: `instanceOfGameStateMinesweeper.copyWith(...)`. - // ignore: library_private_types_in_public_api - _$GameStateMinesweeperCWProxy get copyWith => _$GameStateMinesweeperCWProxyImpl(this); -} - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -GameStateMinesweeper _$GameStateMinesweeperFromJson(Map json) => GameStateMinesweeper( - status: $enumDecodeNullable(_$GameStatusEnumMap, json['status']) ?? GameStatus.idle, - mode: GameModeMinesweeper.fromJson(json['mode'] as String), - board: CellBoard.fromJson(json['board'] as Map), - firstClick: _$recordConvertNullable( - json['firstClick'], - ($jsonValue) => ( - column: ($jsonValue['column'] as num).toInt(), - row: ($jsonValue['row'] as num).toInt(), - ), - ), - playtime: json['playtime'] == null ? Duration.zero : Duration(microseconds: (json['playtime'] as num).toInt()), - ); - -Map _$GameStateMinesweeperToJson(GameStateMinesweeper instance) => { - 'status': _$GameStatusEnumMap[instance.status]!, - 'mode': instance.mode, - 'board': instance.board, - 'playtime': instance.playtime.inMicroseconds, - 'firstClick': instance.firstClick == null - ? null - : { - 'column': instance.firstClick!.column, - 'row': instance.firstClick!.row, - }, - }; - -const _$GameStatusEnumMap = { - GameStatus.running: 'running', - GameStatus.idle: 'idle', - GameStatus.gameOver: 'gameOver', - GameStatus.victory: 'victory', -}; - -$Rec? _$recordConvertNullable<$Rec>( - Object? value, - $Rec Function(Map) convert, -) => - value == null ? null : convert(value as Map); diff --git a/lib/game/minesweeper/i18n.dart b/lib/game/minesweeper/i18n.dart deleted file mode 100644 index 0a2da4eac..000000000 --- a/lib/game/minesweeper/i18n.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:mimir/game/i18n.dart'; -import 'package:mimir/game/minesweeper/entity/mode.dart'; -import 'package:mimir/l10n/common.dart'; - -import 'r.dart'; - -const i18n = _I18n(); - -class _I18n with CommonI18nMixin, CommonGameI18nMixin { - const _I18n(); - - static const ns = "game.${RMinesweeper.name}"; - final records = const _Records(); - - String get title => "$ns.title".tr(); - - String timeSpent(String time) => "$ns.timeSpent".tr(args: [time]); -} - -class _Records { - static const ns = "${_I18n.ns}.records"; - - const _Records(); - - String get title => "$ns.title".tr(); - - String record({ - required GameModeMinesweeper mode, - required int rows, - required int columns, - required int mines, - }) => - "$ns.record".tr(namedArgs: { - "mode": mode.l10n(), - "rows": "$rows", - "columns": "$columns", - "mines": "$mines", - }); -} diff --git a/lib/game/minesweeper/manager/logic.dart b/lib/game/minesweeper/manager/logic.dart deleted file mode 100644 index 3a470f0b9..000000000 --- a/lib/game/minesweeper/manager/logic.dart +++ /dev/null @@ -1,217 +0,0 @@ -import 'package:mimir/game/entity/game_result.dart'; -import 'package:mimir/game/minesweeper/entity/record.dart'; -import 'package:mimir/game/minesweeper/entity/save.dart'; -import 'package:mimir/game/utils.dart'; - -import '../entity/mode.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import "package:flutter/foundation.dart"; -import 'package:logger/logger.dart'; -import '../entity/board.dart'; -import '../entity/cell.dart'; -import '../entity/state.dart'; -import 'package:mimir/game/entity/game_status.dart'; - -import '../storage.dart'; - -// Debug Tool -final logger = Logger(); - -class GameLogic extends StateNotifier { - GameLogic([GameStateMinesweeper? initial]) : super(initial ?? GameStateMinesweeper.byDefault()); - - void initGame({required GameModeMinesweeper gameMode}) { - final board = CellBoard.empty(rows: state.mode.gameRows, columns: state.mode.gameColumns); - state = GameStateMinesweeper(mode: gameMode, board: board); - if (kDebugMode) { - logger.log(Level.info, "Game Init Finished"); - } - } - - void fromSave(SaveMinesweeper save) { - state = GameStateMinesweeper.fromSave(save); - final firstClick = save.firstClick; - dig(cell: state.board.getCell(row: firstClick.row, column: firstClick.column)); - } - - Duration get playtime => state.playtime; - - set playtime(Duration playtime) => state = state.copyWith( - playtime: playtime, - ); - - Cell getCell({required row, required col}) { - return state.board.getCell(row: row, column: col); - } - - void _changeCell({required Cell cell, required CellState state}) { - this.state = this.state.copyWith( - board: this.state.board.changeCell( - row: cell.row, - column: cell.column, - state: state, - ), - ); - } - - void dig({required Cell cell}) { - assert(state.status != GameStatus.gameOver && state.status != GameStatus.victory, "Game is already over"); - // Generating mines on first dig - if (state.status == GameStatus.idle && state.firstClick == null) { - final mode = state.mode; - final firstClick = (row: cell.row, column: cell.column); - state = state.copyWith( - firstClick: firstClick, - status: GameStatus.running, - board: CellBoard.withMines( - rows: mode.gameRows, - columns: mode.gameColumns, - mines: mode.gameMines, - firstClick: firstClick, - ), - ); - } - if (cell.state == CellState.covered) { - _changeCell(cell: cell, state: CellState.blank); - // Check Game State - if (cell.mine) { - onGameOver(); - } else { - _digAroundIfSafe(cell: cell); - if (checkWin()) { - onVictory(); - } - } - } - } - - void onVictory() { - if (!state.status.canPlay) return; - state = state.copyWith( - status: GameStatus.victory, - ); - final firstClick = state.firstClick; - if (firstClick == null) return; - StorageMinesweeper.record.add(RecordMinesweeper.createFrom( - board: state.board, - playtime: state.playtime, - mode: state.mode, - result: GameResult.victory, - firstClick: firstClick, - )); - } - - void onGameOver() { - if (!state.status.canPlay) return; - state = state.copyWith( - status: GameStatus.gameOver, - ); - final firstClick = state.firstClick; - if (firstClick == null) return; - StorageMinesweeper.record.add(RecordMinesweeper.createFrom( - board: state.board, - playtime: state.playtime, - mode: state.mode, - result: GameResult.gameOver, - firstClick: firstClick, - )); - applyGameHapticFeedback(HapticFeedbackIntensity.heavy); - } - - void _digAroundIfSafe({required Cell cell}) { - if (cell.minesAround == 0) { - for (final neighbor in state.board.iterateAround(row: cell.row, column: cell.column)) { - if (neighbor.state == CellState.covered && neighbor.minesAround == 0) { - _changeCell(cell: neighbor, state: CellState.blank); - _digAroundIfSafe(cell: neighbor); - } else if (!neighbor.mine && neighbor.state == CellState.covered && neighbor.minesAround != 0) { - _changeCell(cell: neighbor, state: CellState.blank); - } - } - } - } - - bool digAroundBesidesFlagged({required Cell cell}) { - if (!state.status.canPlay) return false; - bool digAny = false; - if (state.board.countAroundByState(cell: cell, state: CellState.flag) >= cell.minesAround) { - for (final neighbor in state.board.iterateAround(row: cell.row, column: cell.column)) { - if (neighbor.state == CellState.covered) { - dig(cell: neighbor); - digAny = true; - } - } - } - return digAny; - } - - bool flagRestCovered({required Cell cell}) { - if (!state.status.canPlay) return false; - bool flagAny = false; - final coveredCount = state.board.countAroundByState(cell: cell, state: CellState.covered); - if (coveredCount == 0) return false; - final flagCount = state.board.countAroundByState(cell: cell, state: CellState.flag); - if (coveredCount + flagCount == cell.minesAround) { - for (final neighbor in state.board.iterateAround(row: cell.row, column: cell.column)) { - if (neighbor.state == CellState.covered) { - flag(cell: neighbor); - flagAny = true; - } - } - } - return flagAny; - } - - bool checkWin() { - var coveredCells = state.board.countAllByState(state: CellState.covered); - var flagCells = state.board.countAllByState(state: CellState.flag); - var mineCells = state.board.mines; - if (kDebugMode) { - logger.log( - Level.debug, - "mines: $mineCells, covers: $coveredCells, flags: $flagCells", - ); - } - if (coveredCells + flagCells == mineCells) { - return true; - } - return false; - } - - void toggleFlag({required Cell cell}) { - if (!state.status.canPlay) return; - if (cell.state == CellState.flag) { - _changeCell(cell: cell, state: CellState.covered); - } else if (cell.state == CellState.covered) { - _changeCell(cell: cell, state: CellState.flag); - } else { - assert(false, "$cell"); - } - } - - void flag({required Cell cell}) { - if (!state.status.canPlay) return; - if (cell.state == CellState.covered) { - _changeCell(cell: cell, state: CellState.flag); - } else { - assert(false, "$cell"); - } - } - - void removeFlag({required Cell cell}) { - if (!state.status.canPlay) return; - if (cell.state == CellState.flag) { - _changeCell(cell: cell, state: CellState.covered); - } else { - assert(false, "$cell"); - } - } - - Future save() async { - if (state.status.shouldSave && state.firstClick != null) { - await StorageMinesweeper.save.save(state.toSave()); - } else { - await StorageMinesweeper.save.delete(); - } - } -} diff --git a/lib/game/minesweeper/page/game.dart b/lib/game/minesweeper/page/game.dart deleted file mode 100644 index 8e53c7b07..000000000 --- a/lib/game/minesweeper/page/game.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter/material.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/adaptive/multiplatform.dart'; -import 'package:mimir/game/ability/ability.dart'; -import 'package:mimir/game/ability/autosave.dart'; -import 'package:mimir/game/ability/timer.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import 'package:mimir/game/minesweeper/settings.dart'; -import 'package:mimir/game/widget/party_popper.dart'; - -import '../entity/screen.dart'; -import '../entity/state.dart'; -import '../manager/logic.dart'; -import '../../entity/timer.dart'; -import '../storage.dart'; -import '../widget/board.dart'; -import '../widget/hud.dart'; -import '../widget/modal.dart'; -import '../i18n.dart'; - -final stateMinesweeper = StateNotifierProvider.autoDispose((ref) { - return GameLogic(); -}); - -class GameMinesweeper extends ConsumerStatefulWidget { - final bool newGame; - - const GameMinesweeper({ - super.key, - this.newGame = true, - }); - - @override - ConsumerState createState() => _MinesweeperState(); -} - -class _MinesweeperState extends ConsumerState with WidgetsBindingObserver, GameWidgetAbilityMixin { - late TimerWidgetAbility timerAbility; - - GameTimer get timer => timerAbility.timer; - - @override - List createAbility() => [ - AutoSaveWidgetAbility(onSave: onSave), - timerAbility = TimerWidgetAbility(), - ]; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.endOfFrame.then((_) { - timer.addListener((state) { - ref.read(stateMinesweeper.notifier).playtime = state; - }); - final logic = ref.read(stateMinesweeper.notifier); - if (widget.newGame) { - logic.initGame(gameMode: SettingsMinesweeper.$.pref.mode); - } else { - final save = StorageMinesweeper.save.load(); - if (save != null) { - logic.fromSave(save); - timer.state = ref.read(stateMinesweeper).playtime; - } else { - logic.initGame(gameMode: SettingsMinesweeper.$.pref.mode); - timer.state = ref.read(stateMinesweeper).playtime; - } - } - }); - } - - void onSave() { - ref.read(stateMinesweeper.notifier).save(); - } - - void resetGame() { - timer.reset(); - ref.read(stateMinesweeper.notifier).initGame(gameMode: ref.read(stateMinesweeper).mode); - } - - void onGameStateChange(GameStateMinesweeper? former, GameStateMinesweeper current) { - switch (current.status) { - case GameStatus.running: - if (!timer.timerStart) timer.startTimer(); - break; - case GameStatus.idle: - case GameStatus.gameOver: - case GameStatus.victory: - if (timer.timerStart) timer.stopTimer(); - break; - } - } - - @override - Widget build(BuildContext context) { - final state = ref.watch(stateMinesweeper); - ref.listen(stateMinesweeper, onGameStateChange); - final screenSize = MediaQuery.of(context).size; - final screen = Screen( - height: screenSize.height, - width: screenSize.width, - gameRows: state.rows, - gameColumns: state.columns, - ); - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: i18n.title.text(), - actions: [ - PlatformIconButton( - onPressed: () { - resetGame(); - }, - icon: Icon(context.icons.refresh), - ) - ], - ), - body: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const GameHud().padH(8), - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(12)), - child: Center( - child: Stack( - children: [ - GameBoard(screen: screen), - if (state.status == GameStatus.gameOver) - GameOverModal( - resetGame: resetGame, - ) - else if (state.status == GameStatus.victory) - VictoryModal( - resetGame: resetGame, - ), - ], - ), - ), - ), - ], - ), - VictoryPartyPopper( - pop: state.status == GameStatus.victory, - ), - ].stack(), - ); - } -} diff --git a/lib/game/minesweeper/page/index.dart b/lib/game/minesweeper/page/index.dart deleted file mode 100644 index 55d91f0c5..000000000 --- a/lib/game/minesweeper/page/index.dart +++ /dev/null @@ -1,23 +0,0 @@ -// thanks to https://github.com/Henry-Sky/minesweeper - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'game.dart'; - -class GameMinesweeperPage extends StatelessWidget { - final bool newGame; - - const GameMinesweeperPage({ - super.key, - this.newGame = true, - }); - - @override - Widget build(BuildContext context) { - return ProviderScope( - child: GameMinesweeper( - newGame: newGame, - ), - ); - } -} diff --git a/lib/game/minesweeper/page/records.dart b/lib/game/minesweeper/page/records.dart deleted file mode 100644 index 8c53dc4a8..000000000 --- a/lib/game/minesweeper/page/records.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/adaptive/foundation.dart'; -import 'package:mimir/design/adaptive/menu.dart'; -import 'package:mimir/design/adaptive/multiplatform.dart'; -import 'package:mimir/game/entity/game_result.dart'; -import 'package:mimir/game/minesweeper/entity/record.dart'; -import 'package:mimir/game/minesweeper/storage.dart'; -import 'package:mimir/game/page/records.dart'; -import 'package:mimir/l10n/extension.dart'; -import 'package:mimir/intent/qrcode/page/view.dart'; - -import '../qrcode/blueprint.dart'; -import '../i18n.dart'; - -class RecordsMinesweeperPage extends ConsumerStatefulWidget { - const RecordsMinesweeperPage({super.key}); - - @override - ConsumerState createState() => _RecordsMinesweeperPageState(); -} - -class _RecordsMinesweeperPageState extends ConsumerState { - @override - Widget build(BuildContext context) { - return GameRecordsPage( - title: i18n.records.title, - recordStorage: StorageMinesweeper.record, - itemBuilder: (context, record) { - return RecordMinesweeperTile(record: record); - }, - ); - } -} - -class RecordMinesweeperTile extends StatelessWidget { - final RecordMinesweeper record; - - const RecordMinesweeperTile({ - super.key, - required this.record, - }); - - @override - Widget build(BuildContext context) { - return ListTile( - leading: Icon( - record.result == GameResult.victory ? Icons.check : Icons.clear, - color: record.result == GameResult.victory ? Colors.green : Colors.red, - ), - title: i18n.records - .record( - mode: record.mode, - rows: record.rows, - columns: record.columns, - mines: record.mines, - ) - .text(), - trailing: buildMoreActions(context), - subtitle: [ - context.formatYmdhmsNum(record.ts).text(), - i18n.formatPlaytime(record.playtime).text(), - // record.blueprint.text(), - ].column(caa: CrossAxisAlignment.start), - ); - } - - Widget buildMoreActions(BuildContext context) { - return PullDownMenuButton( - itemBuilder: (ctx) => [ - PullDownItem( - icon: context.icons.refresh, - title: i18n.tryAgain, - onTap: () async { - await onHandleBlueprintMinesweeper( - context: context, - blueprint: record.blueprint, - ); - }), - PullDownItem( - icon: context.icons.qrcode, - title: i18n.shareQrCode, - onTap: () { - final qrCodeData = blueprintMinesweeperDeepLink.encodeString( - record.blueprint, - ); - context.showSheet( - (context) => QrCodePage( - title: i18n.title, - data: qrCodeData.toString(), - ), - ); - }, - ), - ], - ); - } -} diff --git a/lib/game/minesweeper/qrcode/blueprint.dart b/lib/game/minesweeper/qrcode/blueprint.dart deleted file mode 100644 index 7f385f97a..000000000 --- a/lib/game/minesweeper/qrcode/blueprint.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:go_router/go_router.dart'; -import 'package:mimir/game/minesweeper/r.dart'; -import 'package:mimir/game/deep_link/blueprint.dart'; - -import '../entity/blueprint.dart'; -import '../storage.dart'; - -const blueprintMinesweeperDeepLink = GameBlueprintDeepLink( - RMinesweeper.name, - onHandleBlueprintMinesweeper, -); - -Future onHandleBlueprintMinesweeper({ - required BuildContext context, - required String blueprint, -}) async { - final blueprintObj = BlueprintMinesweeper.from(blueprint); - final state = blueprintObj.create(); - StorageMinesweeper.save.save(state.toSave()); - context.push("/game/minesweeper?continue"); -} diff --git a/lib/game/minesweeper/r.dart b/lib/game/minesweeper/r.dart deleted file mode 100644 index 93e0043fe..000000000 --- a/lib/game/minesweeper/r.dart +++ /dev/null @@ -1,4 +0,0 @@ -class RMinesweeper { - static const name = "minesweeper"; - static const version = 1; -} diff --git a/lib/game/minesweeper/resolver.dart b/lib/game/minesweeper/resolver.dart deleted file mode 100644 index e212f01fc..000000000 --- a/lib/game/minesweeper/resolver.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:mimir/game/minesweeper/entity/board.dart'; - -class MinesweeperResolver { - final int rowClick; - final int columClick; - - const MinesweeperResolver({ - required this.rowClick, - required this.columClick, - }); - - void resolve(CellBoard bord) {} -} diff --git a/lib/game/minesweeper/settings.dart b/lib/game/minesweeper/settings.dart deleted file mode 100644 index 1a4137f4c..000000000 --- a/lib/game/minesweeper/settings.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:hive/hive.dart'; -import 'package:mimir/storage/hive/init.dart'; -import 'package:mimir/utils/hive.dart'; -import 'package:mimir/utils/json.dart'; - -import 'entity/pref.dart'; - -class _K { - static const ns = "/settings"; - static const pref = '$ns/pref'; -} - -class SettingsMinesweeper { - static final $ = SettingsMinesweeper._(); - - Box get box => HiveInit.gameMinesweeper; - - SettingsMinesweeper._(); - - /// [false] by default. - GamePrefMinesweeper get pref => - decodeJsonObject(box.safeGet(_K.pref), (obj) => GamePrefMinesweeper.fromJson(obj)) ?? - const GamePrefMinesweeper(); - - set pref(GamePrefMinesweeper newV) => box.safePut(_K.pref, encodeJsonObject(newV)); - - late final $pref = box.providerWithDefault( - _K.pref, - get: () => pref, - set: (v) => pref = v, - () => const GamePrefMinesweeper(), - ); -} diff --git a/lib/game/minesweeper/storage.dart b/lib/game/minesweeper/storage.dart deleted file mode 100644 index 72d620999..000000000 --- a/lib/game/minesweeper/storage.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:mimir/game/storage/record.dart'; -import 'package:mimir/game/storage/save.dart'; -import 'package:mimir/storage/hive/init.dart'; - -import 'entity/save.dart'; -import 'entity/record.dart'; -import 'r.dart'; - -class StorageMinesweeper { - static const _ns = "/${RMinesweeper.name}/${RMinesweeper.version}"; - static final save = GameSaveStorage( - () => HiveInit.gameMinesweeper, - prefix: _ns, - serialize: (save) => save.toJson(), - deserialize: SaveMinesweeper.fromJson, - ); - static final record = GameRecordStorage( - () => HiveInit.gameMinesweeper, - prefix: _ns, - serialize: (record) => record.toJson(), - deserialize: RecordMinesweeper.fromJson, - ); -} diff --git a/lib/game/minesweeper/theme.dart b/lib/game/minesweeper/theme.dart deleted file mode 100644 index da2b2ee80..000000000 --- a/lib/game/minesweeper/theme.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; - -// GameInfo Color -final gameOverColor = Colors.red[600]!; -final victoryColor = Colors.green[600]!; -const iconSize = 42.0; -const numberSize = iconSize * 0.7; -const mineSize = iconSize * 0.7; -const flagSize = iconSize * 0.8; - -// Cell Color -const mineColor = Colors.red; -const flagColor = Colors.amber; - -// Blank Cell Number Color -const List numberColors = [ - Colors.green, - Colors.blue, - Colors.orange, - Colors.purple, - Colors.brown, - Colors.amber, - Colors.blueGrey, - Colors.red, -]; - -// Error Info Color -const errorColor = Colors.redAccent; diff --git a/lib/game/minesweeper/utils.dart b/lib/game/minesweeper/utils.dart deleted file mode 100644 index 7539ec5e6..000000000 --- a/lib/game/minesweeper/utils.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'dart:math'; - -import 'entity/board.dart'; - -Iterable generateSymmetricRange(int start, int end) sync* { - start = start.abs(); - end = end.abs(); - for (var i = -end + 1; i < -start + 1; i++) { - yield i; - } - if (start == 0) { - start = 1; - } - for (var i = start; i < end; i++) { - yield i; - } -} - -Iterable<(int, int)> generateCoord(int extensionStep, {int startWith = 0}) sync* { - for (final x in generateSymmetricRange(startWith, extensionStep)) { - for (final y in generateSymmetricRange(startWith, extensionStep)) { - yield (x, y); - } - } -} - -// TODO: generating a game without guesswork -void randomGenerateMines( - CellBoardBuilder board, { - required int mines, - required int rowFirstClick, - required int columnFirstClick, -}) { - final rand = Random(); - final candidates = List.generate( - board.rows * board.columns, (index) => (row: index ~/ board.columns, column: index % board.columns)); - // Clicked cell and one-cell nearby cells can't be mines. - for (final (dx, dy) in CellBoard.nearbyDeltaAndThis) { - final row = rowFirstClick + dx; - final column = columnFirstClick + dy; - candidates.remove((row: row, column: column)); - } - // for (final (dx, dy) in generateCoord(5, startWith: 2)) { - // final row = rowFirstClick + dx; - // final column = columnFirstClick + dy; - // if (rand.nextBool()) { - // candidates.remove((row: row, column: column)); - // } - // } - final maxMines = candidates.length - 1; - assert(mines <= maxMines, "The max mine is $maxMines, but $mines is given."); - var remaining = min(mines, maxMines); - board.mines = remaining; - while (candidates.isNotEmpty && remaining > 0) { - final index = rand.nextInt(candidates.length); - final (:row, :column) = candidates[index]; - final cell = board.getCell(row: row, column: column); - if (!cell.mine) { - cell.mine = true; - // count as mine created - for (final neighbor in board.iterateAround(row: row, column: column)) { - neighbor.minesAround += 1; - } - remaining--; - candidates.removeAt(index); - } - } -} diff --git a/lib/game/minesweeper/widget/board.dart b/lib/game/minesweeper/widget/board.dart deleted file mode 100644 index 70196d63c..000000000 --- a/lib/game/minesweeper/widget/board.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter/material.dart'; -import 'package:rettulf/rettulf.dart'; -import '../entity/cell.dart'; -import '../entity/screen.dart'; -import 'cell.dart'; -import '../page/game.dart'; - -class GameBoard extends ConsumerWidget { - final Screen screen; - - const GameBoard({ - super.key, - required this.screen, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final board = ref.read(stateMinesweeper.select((state) => state.board)); - final rows = board.rows; - final columns = board.columns; - var boardWidth = screen.getBoardSize().width; - var boardHeight = screen.getBoardSize().height; - final portrait = context.isPortrait; - if (!portrait) { - (boardWidth, boardHeight) = (boardHeight, boardWidth); - } - final cellWidth = screen.getCellWidth(); - - return SizedBox( - width: boardWidth, - height: boardHeight, - child: Stack( - children: List.generate(rows * columns, (i) { - final row = i ~/ columns; - final column = i % columns; - final cell = board.getCell(row: row, column: column); - return Positioned( - left: (portrait ? column : row) * cellWidth, - top: (portrait ? row : column) * cellWidth, - child: buildCell(cell, cellWidth), - ); - }), - ), - ); - } - - Widget buildCell(Cell cell, double size) { - return ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(12)), - child: CellWidget(cell: cell).sizedAll(size), - ); - } -} diff --git a/lib/game/minesweeper/widget/cell.dart b/lib/game/minesweeper/widget/cell.dart deleted file mode 100644 index c23d89f16..000000000 --- a/lib/game/minesweeper/widget/cell.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter/material.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import '../entity/cell.dart'; -import 'cell/button.dart'; -import 'cell/cover.dart'; -import 'cell/flag.dart'; -import 'cell/mine.dart'; -import 'cell/number.dart'; -import '../page/game.dart'; - -class CellWidget extends ConsumerWidget { - final Cell cell; - - const CellWidget({ - super.key, - required this.cell, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return CellButton( - cell: cell, - child: CellContent( - cell: cell, - ), - ); - } -} - -class CellContent extends ConsumerWidget { - final Cell cell; - - const CellContent({ - super.key, - required this.cell, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final gameStatus = ref.watch(stateMinesweeper.select((state) => state.status)); - final bottom = buildBottom(cell); - return Stack( - alignment: Alignment.center, - children: [ - if (bottom != null) bottom.center(), - Opacity( - opacity: (gameStatus == GameStatus.gameOver || gameStatus == GameStatus.victory) && cell.mine ? 0.5 : 1.0, - child: CellCover(visible: cell.state.showCover), - ), - CellFlag(visible: cell.state.showFlag), - ], - ); - } - - Widget? buildBottom(Cell cell) { - if (cell.mine) { - return const Mine(); - } else if (cell.minesAround > 0) { - return MinesAroundNumber(minesAround: cell.minesAround); - } - return null; - } -} diff --git a/lib/game/minesweeper/widget/cell/button.dart b/lib/game/minesweeper/widget/cell/button.dart deleted file mode 100644 index 34c7b4b4f..000000000 --- a/lib/game/minesweeper/widget/cell/button.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import 'package:mimir/game/utils.dart'; -import '../../entity/cell.dart'; -import '../../page/game.dart'; - -class CellButton extends ConsumerWidget { - const CellButton({ - super.key, - required this.cell, - required this.child, - }); - - final Cell cell; - final Widget child; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final cellState = cell.state; - if (cellState == CellState.blank && cell.minesAround == 0) { - return child; - } - final gameState = ref.watch(stateMinesweeper.select((state) => state.status)); - return GestureDetector( - onTap: (gameState == GameStatus.running || gameState == GameStatus.idle) && cellState.showCover - ? () { - final manager = ref.read(stateMinesweeper.notifier); - // Click a Cover Cell => Blank - if (!cellState.showFlag) { - manager.dig(cell: cell); - applyGameHapticFeedback(); - } else { - // Click a Flag Cell => Cancel Flag (Covered) - manager.removeFlag(cell: cell); - } - } - : null, - onDoubleTap: gameState == GameStatus.running && !cellState.showCover - ? () { - final manager = ref.read(stateMinesweeper.notifier); - bool anyChanged = false; - anyChanged |= manager.digAroundBesidesFlagged(cell: cell); - anyChanged |= manager.flagRestCovered(cell: cell); - if (anyChanged) { - applyGameHapticFeedback(); - } - } - : null, - onLongPress: gameState == GameStatus.running && cellState.showCover - ? () { - final manager = ref.read(stateMinesweeper.notifier); - manager.toggleFlag(cell: cell); - applyGameHapticFeedback(); - } - : null, - onSecondaryTap: gameState == GameStatus.running && cellState.showCover - ? () { - final manager = ref.read(stateMinesweeper.notifier); - manager.toggleFlag(cell: cell); - applyGameHapticFeedback(); - } - : null, - child: child, - ); - } -} diff --git a/lib/game/minesweeper/widget/cell/cover.dart b/lib/game/minesweeper/widget/cell/cover.dart deleted file mode 100644 index e185cc621..000000000 --- a/lib/game/minesweeper/widget/cell/cover.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:rettulf/rettulf.dart'; - -class CellCover extends StatelessWidget { - final bool visible; - - const CellCover({ - super.key, - required this.visible, - }); - - @override - Widget build(BuildContext context) { - return AnimatedOpacity( - opacity: visible ? 1 : 0, - curve: Curves.ease, - duration: visible ? Duration.zero : Durations.long1, - child: Container( - margin: const EdgeInsets.all(1), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.0), - color: context.colorScheme.surfaceContainerHighest, - ), - ), - ); - } -} diff --git a/lib/game/minesweeper/widget/cell/flag.dart b/lib/game/minesweeper/widget/cell/flag.dart deleted file mode 100644 index 068d96ffa..000000000 --- a/lib/game/minesweeper/widget/cell/flag.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../theme.dart'; - -class CellFlag extends StatelessWidget { - const CellFlag({ - super.key, - required this.visible, - }); - - final duration = Durations.medium4; - final curve = Curves.ease; - final bool visible; - - @override - Widget build(BuildContext context) { - return AnimatedOpacity( - opacity: visible ? 1 : 0, - duration: duration, - curve: curve, - child: AnimatedScale( - scale: visible ? 1 : 0.2, - duration: duration, - curve: curve, - child: const Icon( - Icons.flag, - size: flagSize, - color: flagColor, - ), - ), - ); - } -} diff --git a/lib/game/minesweeper/widget/cell/mine.dart b/lib/game/minesweeper/widget/cell/mine.dart deleted file mode 100644 index 79051530e..000000000 --- a/lib/game/minesweeper/widget/cell/mine.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../theme.dart'; - -class Mine extends StatelessWidget { - const Mine({super.key}); - - @override - Widget build(BuildContext context) { - return const Icon( - Icons.gps_fixed, - size: mineSize, - color: mineColor, - ); - } -} diff --git a/lib/game/minesweeper/widget/cell/number.dart b/lib/game/minesweeper/widget/cell/number.dart deleted file mode 100644 index aca362f6a..000000000 --- a/lib/game/minesweeper/widget/cell/number.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../theme.dart'; - -class MinesAroundNumber extends StatelessWidget { - final int minesAround; - - const MinesAroundNumber({ - super.key, - required this.minesAround, - }); - - @override - Widget build(BuildContext context) { - return Text( - minesAround.toString(), - textAlign: TextAlign.center, - style: TextStyle( - color: numberColors[minesAround - 1], - fontWeight: FontWeight.w900, - fontSize: numberSize, - ), - ); - } -} diff --git a/lib/game/minesweeper/widget/hud.dart b/lib/game/minesweeper/widget/hud.dart deleted file mode 100644 index 1fc1e345c..000000000 --- a/lib/game/minesweeper/widget/hud.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter/material.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import '../entity/cell.dart'; -import '../theme.dart'; -import '../page/game.dart'; -import '../i18n.dart'; - -class GameHud extends ConsumerWidget { - const GameHud({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final state = ref.watch(stateMinesweeper); - final textTheme = context.textTheme; - return ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(12), - ), - child: [ - Container( - decoration: BoxDecoration( - color: context.colorScheme.secondaryContainer, - ), - child: [ - const Icon(Icons.videogame_asset_outlined), - state.status == GameStatus.running - ? MinesAndFlags( - flags: state.board.countAllByState(state: CellState.flag), - mines: state.board.mines, - ) - : Text( - state.mode.l10n(), - style: context.textTheme.bodyLarge, - ), - ].row(maa: MainAxisAlignment.spaceAround), - ).expanded(), - Container( - decoration: BoxDecoration( - color: context.colorScheme.tertiaryContainer, - ), - child: [ - const Icon(Icons.alarm), - Text( - i18n.formatPlaytime(state.playtime), - style: textTheme.bodyLarge, - ), - ].row(maa: MainAxisAlignment.spaceAround), - ).expanded(), - ].row(maa: MainAxisAlignment.center, caa: CrossAxisAlignment.stretch).sized(h: 50), - ); - } -} - -class GameModeButton extends StatefulWidget { - const GameModeButton({super.key}); - - @override - State createState() => _GameModeButtonState(); -} - -class _GameModeButtonState extends State { - @override - Widget build(BuildContext context) { - return const Placeholder(); - } -} - -class MinesAndFlags extends StatelessWidget { - final int flags; - final int mines; - - const MinesAndFlags({ - super.key, - required this.flags, - required this.mines, - }); - - @override - Widget build(BuildContext context) { - final textTheme = context.textTheme; - return Row( - children: [ - Text( - " $flags ", - style: textTheme.bodyLarge, - ), - const Icon( - Icons.flag_outlined, - color: flagColor, - ), - Text( - "/ $mines ", - style: textTheme.bodyLarge, - ), - const Icon( - Icons.gps_fixed, - color: mineColor, - ), - ], - ); - } -} diff --git a/lib/game/minesweeper/widget/modal.dart b/lib/game/minesweeper/widget/modal.dart deleted file mode 100644 index 70c7b8ea6..000000000 --- a/lib/game/minesweeper/widget/modal.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/game/minesweeper/page/game.dart'; -import '../theme.dart'; -import '../i18n.dart'; - -class GameOverModal extends ConsumerWidget { - final void Function() resetGame; - - const GameOverModal({ - super.key, - required this.resetGame, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Positioned.fill( - child: InkWell( - borderRadius: BorderRadius.circular(12.0), - onTap: () { - resetGame(); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.0), - color: gameOverColor.withOpacity(0.5), - ), - child: Text( - i18n.gameOver, - style: const TextStyle( - fontSize: 64.0, - ), - ).center(), - ), - ), - ); - } -} - -class VictoryModal extends ConsumerWidget { - final void Function() resetGame; - - const VictoryModal({ - super.key, - required this.resetGame, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final playTime = ref.watch(stateMinesweeper.select((state) => state.playtime)); - return Positioned.fill( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.0), - color: victoryColor.withOpacity(0.5), - ), - child: InkWell( - borderRadius: BorderRadius.circular(12.0), - onTap: () { - resetGame(); - }, - child: Text( - "${i18n.youWin}\n${i18n.timeSpent(i18n.formatPlaytime(playTime))}", - style: const TextStyle( - fontSize: 64.0, - ), - ).center(), - ), - ), - ); - } -} diff --git a/lib/game/page/records.dart b/lib/game/page/records.dart deleted file mode 100644 index 5d691c4c5..000000000 --- a/lib/game/page/records.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/adaptive/multiplatform.dart'; -import 'package:mimir/design/adaptive/swipe.dart'; -import 'package:mimir/design/widget/common.dart'; -import 'package:mimir/game/entity/record.dart'; -import 'package:mimir/game/storage/record.dart'; -import "../i18n.dart"; - -class GameRecordsPage extends ConsumerStatefulWidget { - final String title; - final GameRecordStorage recordStorage; - final Widget Function(BuildContext context, TRecord record) itemBuilder; - - const GameRecordsPage({ - super.key, - required this.title, - required this.recordStorage, - required this.itemBuilder, - }); - - @override - ConsumerState> createState() => _RecordsMinesweeperPageState(); -} - -class _RecordsMinesweeperPageState extends ConsumerState> { - @override - Widget build(BuildContext context) { - final rows = ref.watch(widget.recordStorage.table.$rowsWithId).reversed.toList(); - return Scaffold( - appBar: AppBar( - title: widget.title.text(), - actions: [ - PlatformIconButton( - icon: Icon(context.icons.delete), - onPressed: () { - widget.recordStorage.clear(); - }, - ), - ], - ), - body: CustomScrollView( - slivers: [ - if (rows.isEmpty) - SliverFillRemaining( - child: LeavingBlank( - icon: Icons.inbox_outlined, - desc: i18n.noGameRecords, - ), - ) - else - SliverList.builder( - itemCount: rows.length, - itemBuilder: (ctx, i) { - final row = rows[i]; - return WithSwipeAction( - childKey: ValueKey(row.id), - right: SwipeAction.delete( - icon: ctx.icons.delete, - action: () { - widget.recordStorage.table.delete(row.id); - }, - ), - child: widget.itemBuilder(ctx, row.row), - ); - }, - ), - ], - ), - ); - } -} diff --git a/lib/game/page/settings.dart b/lib/game/page/settings.dart deleted file mode 100644 index f45191824..000000000 --- a/lib/game/page/settings.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mimir/settings/settings.dart'; -import 'package:rettulf/rettulf.dart'; -import '../i18n.dart'; - -class GameSettingsPage extends ConsumerStatefulWidget { - const GameSettingsPage({ - super.key, - }); - - @override - ConsumerState createState() => _GameSettingsPageState(); -} - -class _GameSettingsPageState extends ConsumerState { - @override - Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - physics: const RangeMaintainingScrollPhysics(), - slivers: [ - SliverAppBar.large( - pinned: true, - snap: false, - floating: false, - title: i18n.navigation.text(), - ), - SliverList.list( - children: const [ - HapticFeedbackTile(), - ShowGameNavigationTile(), - ], - ), - ], - ), - ); - } -} - -class HapticFeedbackTile extends ConsumerWidget { - const HapticFeedbackTile({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final on = ref.watch(Settings.game.$enableHapticFeedback) ?? true; - return SwitchListTile.adaptive( - secondary: const Icon(Icons.vibration), - title: i18n.settings.enableHapticFeedback.text(), - subtitle: i18n.settings.enableHapticFeedbackDesc.text(), - value: on, - onChanged: (newV) { - ref.read(Settings.game.$enableHapticFeedback.notifier).set(newV); - }, - ); - } -} - -class ShowGameNavigationTile extends ConsumerWidget { - const ShowGameNavigationTile({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final on = ref.watch(Settings.game.$showGameNavigation) ?? true; - return SwitchListTile.adaptive( - secondary: const Icon(Icons.vibration), - title: i18n.settings.showGameNavigation.text(), - subtitle: i18n.settings.showGameNavigationDesc.text(), - value: on, - onChanged: (newV) { - ref.read(Settings.game.$showGameNavigation.notifier).set(newV); - }, - ); - } -} diff --git a/lib/game/settings.dart b/lib/game/settings.dart deleted file mode 100644 index 2f24c7dff..000000000 --- a/lib/game/settings.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:hive_flutter/hive_flutter.dart'; -import 'package:mimir/utils/hive.dart'; - -class _K { - static const ns = "/game"; - static const enableHapticFeedback = "$ns/enableHapticFeedback"; - static const showGameNavigation = "$ns/showGameNavigation"; -} - -class GameSettings { - final Box box; - - GameSettings(this.box); - - bool get enableHapticFeedback => box.safeGet(_K.enableHapticFeedback) ?? true; - - set enableHapticFeedback(bool newV) => box.safePut(_K.enableHapticFeedback, newV); - - late final $enableHapticFeedback = box.provider(_K.enableHapticFeedback); - - bool get showGameNavigation => box.safeGet(_K.showGameNavigation) ?? true; - - set showGameNavigation(bool newV) => box.safePut(_K.showGameNavigation, newV); - - late final $showGameNavigation = box.providerWithDefault(_K.showGameNavigation, () => true); -} diff --git a/lib/game/storage/record.dart b/lib/game/storage/record.dart deleted file mode 100644 index cf1a9683d..000000000 --- a/lib/game/storage/record.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:hive_flutter/hive_flutter.dart'; -import 'package:mimir/game/entity/record.dart'; -import 'package:mimir/storage/hive/table.dart'; -import 'package:mimir/utils/hive.dart'; - -class GameRecordStorage { - final Box Function() box; - final String prefix; - final TRecord Function(Map json) deserialize; - final Map Function(TRecord save) serialize; - - GameRecordStorage( - this.box, { - required String prefix, - required this.serialize, - required this.deserialize, - }) : prefix = "$prefix/record"; - - late final table = HiveTable.withUuid( - base: prefix, - box: box(), - useJson: (fromJson: deserialize, toJson: serialize), - ); - - String add(TRecord save) { - final id = table.add(save); - return id; - } - - void delete({required String id}) { - table.delete(id); - } - - void clear() { - table.drop(); - } - - late final $recordOf = box().providerFamily( - (id) => "$prefix/$id", - get: (id) => table[id], - set: (id, v) async { - if (v == null) { - delete(id: id); - } else { - table[id] = v; - } - }, - ); -} diff --git a/lib/game/storage/save.dart b/lib/game/storage/save.dart deleted file mode 100644 index 352017c90..000000000 --- a/lib/game/storage/save.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:flutter/cupertino.dart'; -import 'package:hive_flutter/hive_flutter.dart'; -import 'package:mimir/utils/hive.dart'; - -Map _defaultToJson(dynamic obj) { - return obj.toJson(); -} - -class GameSaveStorage { - final Box Function() _box; - final String prefix; - final TSave Function(Map json) deserialize; - final Map Function(TSave save) serialize; - - GameSaveStorage( - this._box, { - required this.prefix, - this.serialize = _defaultToJson, - required this.deserialize, - }); - - String get _prefix => "$prefix/save"; - - Future save(TSave save, {int slot = 0}) async { - final json = serialize(save); - final str = jsonEncode(json); - await _box().safePut("$_prefix/$slot", str); - } - - Future delete({int slot = 0}) async { - await _box().delete("$_prefix/$slot"); - } - - TSave? load({int slot = 0}) { - final str = _box().safeGet("$_prefix/$slot"); - if (str == null) return null; - try { - final json = jsonDecode(str); - final save = deserialize(json); - return save; - } catch (e) { - return null; - } - } - - bool exists({int slot = 0}) { - return _box().containsKey("$_prefix/$slot"); - } - - late final $saveOf = _box().providerFamily( - (slot) => "$_prefix/$slot", - get: (slot) => load(slot: slot), - set: (slot, v) async { - if (v == null) { - await delete(slot: slot); - } else { - await save(v, slot: slot); - } - }, - ); - - late final $saveExistsOf = _box().existsChangeProviderFamily( - (slot) => "$_prefix/$slot", - ); - - Listenable listen({int slot = 0}) { - return _box().listenable(keys: ["$_prefix/$slot"]); - } -} diff --git a/lib/game/sudoku/card.dart b/lib/game/sudoku/card.dart deleted file mode 100644 index 41a9f4800..000000000 --- a/lib/game/sudoku/card.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/adaptive/dialog.dart'; -import 'package:mimir/game/sudoku/entity/pref.dart'; -import 'package:mimir/game/sudoku/storage.dart'; -import 'package:mimir/game/widget/mode.dart'; -import 'entity/mode.dart'; -import 'package:mimir/game/widget/card.dart'; - -import 'i18n.dart'; -import 'settings.dart'; - -class GameAppCardSudoku extends ConsumerStatefulWidget { - const GameAppCardSudoku({super.key}); - - @override - ConsumerState createState() => _GameAppCardSudokuState(); -} - -class _GameAppCardSudokuState extends ConsumerState { - @override - Widget build(BuildContext context) { - return OfflineGameAppCard( - name: i18n.title, - baseRoute: "/sudoku", - save: StorageSudoku.save, - supportRecords: true, - view: buildGameModeCard().align(at: Alignment.centerLeft), - ); - } - - Widget buildGameModeCard() { - final pref = ref.watch(SettingsSudoku.$.$pref); - return GameModeSelectorCard( - all: GameModeSudoku.values, - current: pref.mode, - onChanged: (newMode) async { - if (StorageSudoku.save.exists()) { - final confirm = await context.showActionRequest( - desc: i18n.changeGameModeRequest, - action: i18n.changeGameModeAction(newMode.l10n()), - cancel: i18n.cancel, - ); - if (confirm != true) return; - } - ref.read(SettingsSudoku.$.$pref.notifier).set(pref.copyWith( - mode: newMode, - )); - StorageSudoku.save.delete(); - }, - ); - } -} diff --git a/lib/game/sudoku/entity/blueprint.dart b/lib/game/sudoku/entity/blueprint.dart deleted file mode 100644 index 6f4e6bb30..000000000 --- a/lib/game/sudoku/entity/blueprint.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:meta/meta.dart'; -import 'package:mimir/game/entity/blueprint.dart'; -import 'package:mimir/intent/deep_link/utils.dart'; -import 'package:mimir/utils/byte_io/reader.dart'; -import 'package:mimir/utils/byte_io/writer.dart'; - -import 'board.dart'; -import 'mode.dart'; -import 'state.dart'; - -@immutable -class BlueprintSudoku implements GameBlueprint { - final SudokuBoard board; - final GameModeSudoku mode; - - const BlueprintSudoku({ - required this.board, - required this.mode, - }); - - factory BlueprintSudoku.from(String data) { - final bytes = decodeBytesFromUrl(data); - final reader = ByteReader(bytes); - final modeRaw = reader.strUtf8(); - final mode = GameModeSudoku.fromJson(modeRaw); - final board = SudokuBoard.readBlueprint(reader); - return BlueprintSudoku( - board: board, - mode: mode, - ); - } - - @override - String build() { - final writer = ByteWriter(512); - writer.strUtf8(mode.toJson()); - board.writeBlueprint(writer); - final bytes = writer.build(); - return encodeBytesForUrl(bytes); - } - - @override - GameStateSudoku create() { - return GameStateSudoku.newGame( - mode: mode, - board: board, - ); - } -} diff --git a/lib/game/sudoku/entity/board.dart b/lib/game/sudoku/entity/board.dart deleted file mode 100644 index 5f8609548..000000000 --- a/lib/game/sudoku/entity/board.dart +++ /dev/null @@ -1,320 +0,0 @@ -import 'package:copy_with_extension/copy_with_extension.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/utils/byte_io/byte_io.dart'; -import 'package:mimir/utils/list2d/list2d.dart'; -import 'package:sudoku_solver_generator/sudoku_solver_generator.dart'; - -import 'save.dart'; - -part "board.g.dart"; - -const sudokuSides = 9; - -@immutable -@JsonSerializable() -@CopyWith(skipFields: true) -class SudokuCell { - static const emptyInputNumber = 0; - static const disableInputNumber = -1; - final int index; - - /// A negative value (e.g., -1) indicates a pre-filled cell generated by the puzzle. - /// The user cannot modify this value. - /// `0` means the cell is empty and awaits user input. - final int userInput; - - /// The correct value that the user should fill in the cell (1 to 9). - final int correctValue; - - const SudokuCell({ - required this.index, - this.userInput = SudokuCell.disableInputNumber, - this.correctValue = SudokuCell.emptyInputNumber, - }) : assert(correctValue == 0 || (1 <= correctValue && correctValue <= 9), - "The puzzle should generate correct value in [1,9] but $correctValue"); - - bool get isPuzzle => userInput < 0; - - bool get canUserInput => userInput >= 0; - - bool get emptyInput { - assert(userInput >= 0, "Developer should check `isPuzzle` before access this"); - return userInput == 0; - } - - bool get isSolved { - assert(userInput >= 0, "Developer should check `isPuzzle` before access this"); - return userInput == correctValue; - } - - @override - bool operator ==(Object other) { - return other is SudokuCell && - runtimeType == other.runtimeType && - userInput == other.userInput && - correctValue == other.correctValue; - } - - @override - int get hashCode => Object.hash(userInput, correctValue); - - factory SudokuCell.fromJson(Map json) => _$SudokuCellFromJson(json); - - Map toJson() => _$SudokuCellToJson(this); - - @override - String toString() => - "(${SudokuBoard.getRowFrom(index)},${SudokuBoard.getColumnFrom(index)}) ${canUserInput ? "$userInput/$correctValue" : correctValue}"; - - void serialize(ByteWriter writer) { - writer.uint8(correctValue); - writer.int8(userInput); - } - - static SudokuCell deserialize(ByteReader reader, int index) { - return SudokuCell( - index: index, - correctValue: reader.uint8(), - userInput: reader.int8(), - ); - } -} - -@immutable -extension type const SudokuBoard(List2D _cells) { -// class SudokuBoard { -// final List2D _cells; -// -// const SudokuBoard(this._cells); - factory SudokuBoard.generate({required int emptySquares}) { - final generator = SudokuGenerator(emptySquares: emptySquares, uniqueSolution: true); - final puzzle = generator.newSudoku; - final solved = generator.newSudokuSolved; - return SudokuBoard(List2D.generate( - sudokuSides, - sudokuSides, - (row, column, index) => SudokuCell( - index: index, - userInput: puzzle[row][column] == 0 ? 0 : -1, - correctValue: solved[row][column], - ), - )); - } - - factory SudokuBoard.byDefault() { - return SudokuBoard( - List2D.generate( - sudokuSides, - sudokuSides, - (row, column, index) => SudokuCell( - index: index, - ), - ), - ); - } - - void writeBlueprint(ByteWriter writer) { - assert(_cells.length == sudokuSides * sudokuSides); - writer.uint8(_cells.length); - for (final cell in _cells) { - writer.b(cell.isPuzzle); - writer.uint8(cell.correctValue); - } - } - - static SudokuBoard readBlueprint(ByteReader reader) { - final len = reader.uint8(); - final cells = []; - for (var i = 0; i < len; i++) { - final isPuzzle = reader.b(); - final correctValue = reader.uint8(); - cells.add(SudokuCell( - index: i, - userInput: isPuzzle ? SudokuCell.disableInputNumber : SudokuCell.emptyInputNumber, - correctValue: correctValue, - )); - } - assert(cells.length == sudokuSides * sudokuSides); - return SudokuBoard(List2D.from1D(sudokuSides, sudokuSides, cells)); - } - - bool get isSolved { - for (final cell in _cells) { - if (cell.isPuzzle) continue; - if (!cell.isSolved) return false; - } - return true; - } - - bool canFill({ - required int cellIndex, - required int number, - }) { - final cell = _cells.getByIndex(cellIndex); - if (!cell.canUserInput) return false; - return true; - } - - Iterable relatedOf(int cellIndex) sync* { - final zone = getZone(SudokuBoardZone.getZoneIndexByIndex(cellIndex)); - for (final cell in zone._cells) { - if (cell.index != cellIndex) yield cell; // exclude self - } - for (final cell in _cells.rowAt(_cells.getRowFrom(cellIndex))) { - if (cell.index != cellIndex) yield cell; // exclude self - } - for (final cell in _cells.columnAt(_cells.getColumnFrom(cellIndex))) { - if (cell.index != cellIndex) yield cell; // exclude self - } - } - - SudokuCell getCellByIndex(int cellIndex) { - return _cells.getByIndex(cellIndex); - } - - static int getRowFrom(int cellIndex) => cellIndex ~/ sudokuSides; - static int getColumnFrom(int cellIndex) => cellIndex % sudokuSides; - - SudokuBoard changeCell(int cellIndex, int userInput) { - final oldCells = _cells; - final newCell = oldCells.getByIndex(cellIndex).copyWith( - userInput: userInput, - ); - final newCells = List2D.of(oldCells)..setByIndex(cellIndex, newCell); - return SudokuBoard(newCells); - } - - bool isCellOnEdge(int cellIndex) { - return _cells.onEdge(_cells.getRowFrom(cellIndex), _cells.getColumnFrom(cellIndex)); - } - - SudokuBoardZone getZone(int zoneIndex) { - return SudokuBoardZone(this, zoneIndex); - } - - SudokuBoardZone getZoneWhereCell(SudokuCell cell) { - final zoneIndex = SudokuBoardZone.getZoneIndexByIndex(cell.index); - return getZone(zoneIndex); - } - - int getZoneIndexWhereCell(SudokuCell cell) { - return SudokuBoardZone.getZoneIndexByIndex(cell.index); - } - - Edge2D? cellOnWhichEdge(SudokuCell cell) { - return _cells.onWhichEdge(_cells.getRowFrom(cell.index), _cells.getColumnFrom(cell.index)); - } - - int? findNextCell( - int startCellIndex, - AxisDirection dir, { - bool allowFilled = true, - bool ignorePuzzle = true, - }) { - final row = getRowFrom(startCellIndex); - final column = getColumnFrom(startCellIndex); - switch (dir) { - case AxisDirection.up: - case AxisDirection.down: - for (final cell in _cells.iterateColumnStartWith( - row, - column, - incremental: dir == AxisDirection.down, - )) { - if (cell.index == startCellIndex) continue; - if (!ignorePuzzle) return cell.index; - if (cell.canUserInput && (cell.emptyInput || (allowFilled && !cell.isSolved))) { - return cell.index; - } - } - break; - case AxisDirection.right: - case AxisDirection.left: - for (final cell in _cells.iterateRowStartWith( - row, - column, - incremental: dir == AxisDirection.right, - )) { - if (cell.index == startCellIndex) continue; - if (!ignorePuzzle) return cell.index; - if (cell.canUserInput && (cell.emptyInput || (allowFilled && !cell.isSolved))) { - return cell.index; - } - } - break; - } - return null; - } - - factory SudokuBoard.fromJson(dynamic json) { - return SudokuBoard( - List2D.fromJson(json, (value) => SudokuCell.fromJson(value as Map)), - ); - } - - dynamic toJson() { - return _cells; - } - - factory SudokuBoard.fromSave(SaveSudoku save) { - final cells = save.cells.mapIndexed( - (row, column, index, cell) => - SudokuCell(index: index, userInput: cell.userInput, correctValue: cell.correctValue), - ); - return SudokuBoard(cells); - } - - List2D toSave() { - return _cells.map((cell) => Cell4Save(userInput: cell.userInput, correctValue: cell.correctValue)); - } -} - -@immutable -class SudokuBoardZone { - final int zoneIndex; - final SudokuBoard parent; - final List2D _cells; - - SudokuBoardZone( - this.parent, - this.zoneIndex, - ) : _cells = parent._cells.subview( - rows: 3, - columns: 3, - rowOffset: (zoneIndex ~/ 3) * 3, - columnOffset: (zoneIndex % 3) * 3, - ); - - int get parentRowOffset => (zoneIndex ~/ 3) * 3; - - int get parentColumnOffset => (zoneIndex % 3) * 3; - static int getZoneIndexOf(int row, int column) { - int x = column ~/ 3; - int y = row ~/ 3; - return y * 3 + x; - } - - static int getZoneIndexByIndex(int boardIndex) { - return getZoneIndexOf(boardIndex ~/ 9, boardIndex % 9); - } - - ({int localRow, int localColumn}) mapBoardIndexToLocal(int boardIndex) { - final parentRow = SudokuBoard.getRowFrom(boardIndex); - final parentColumn = SudokuBoard.getColumnFrom(boardIndex); - final localRow = (parentRow - parentRowOffset) % 3; - final localColumn = (parentColumn - parentColumnOffset) % 3; - assert(0 <= localRow && localRow < 3, "$localRow not in [0,3)"); - assert(0 <= localColumn && localColumn < 3, "$localColumn not in [0,3)"); - return (localRow: localRow, localColumn: localColumn); - } - - Edge2D? onWhichEdge(int boardIndex) { - final (:localRow, :localColumn) = mapBoardIndexToLocal(boardIndex); - return _cells.onWhichEdge(localRow, localColumn); - } - - Edge2D? cellOnWhichEdge(SudokuCell cell) { - return onWhichEdge(cell.index); - } -} diff --git a/lib/game/sudoku/entity/board.g.dart b/lib/game/sudoku/entity/board.g.dart deleted file mode 100644 index a1aa5fe11..000000000 --- a/lib/game/sudoku/entity/board.g.dart +++ /dev/null @@ -1,79 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'board.dart'; - -// ************************************************************************** -// CopyWithGenerator -// ************************************************************************** - -abstract class _$SudokuCellCWProxy { - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// SudokuCell(...).copyWith(id: 12, name: "My name") - /// ```` - SudokuCell call({ - int? index, - int? userInput, - int? correctValue, - }); -} - -/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfSudokuCell.copyWith(...)`. -class _$SudokuCellCWProxyImpl implements _$SudokuCellCWProxy { - const _$SudokuCellCWProxyImpl(this._value); - - final SudokuCell _value; - - @override - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// SudokuCell(...).copyWith(id: 12, name: "My name") - /// ```` - SudokuCell call({ - Object? index = const $CopyWithPlaceholder(), - Object? userInput = const $CopyWithPlaceholder(), - Object? correctValue = const $CopyWithPlaceholder(), - }) { - return SudokuCell( - index: index == const $CopyWithPlaceholder() || index == null - ? _value.index - // ignore: cast_nullable_to_non_nullable - : index as int, - userInput: userInput == const $CopyWithPlaceholder() || userInput == null - ? _value.userInput - // ignore: cast_nullable_to_non_nullable - : userInput as int, - correctValue: correctValue == const $CopyWithPlaceholder() || correctValue == null - ? _value.correctValue - // ignore: cast_nullable_to_non_nullable - : correctValue as int, - ); - } -} - -extension $SudokuCellCopyWith on SudokuCell { - /// Returns a callable class that can be used as follows: `instanceOfSudokuCell.copyWith(...)`. - // ignore: library_private_types_in_public_api - _$SudokuCellCWProxy get copyWith => _$SudokuCellCWProxyImpl(this); -} - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SudokuCell _$SudokuCellFromJson(Map json) => SudokuCell( - index: (json['index'] as num).toInt(), - userInput: (json['userInput'] as num?)?.toInt() ?? SudokuCell.disableInputNumber, - correctValue: (json['correctValue'] as num?)?.toInt() ?? SudokuCell.emptyInputNumber, - ); - -Map _$SudokuCellToJson(SudokuCell instance) => { - 'index': instance.index, - 'userInput': instance.userInput, - 'correctValue': instance.correctValue, - }; diff --git a/lib/game/sudoku/entity/mode.dart b/lib/game/sudoku/entity/mode.dart deleted file mode 100644 index f642f0b45..000000000 --- a/lib/game/sudoku/entity/mode.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/game/entity/game_mode.dart'; - -@JsonSerializable(createToJson: false, createFactory: false) -class GameModeSudoku extends GameMode { - final int blanks; - final bool enableFillerHint; - static const beginner = GameModeSudoku._( - name: "beginner", - blanks: 18, - enableFillerHint: true, - ); - static const easy = GameModeSudoku._( - name: "easy", - blanks: 27, - enableFillerHint: true, - ); - static const medium = GameModeSudoku._( - name: "medium", - blanks: 36, - ); - static const hard = GameModeSudoku._( - name: "hard", - blanks: 54, - ); - - static final name2mode = { - "beginner": beginner, - "easy": easy, - "medium": medium, - "hard": hard, - }; - - static final values = [ - beginner, - easy, - medium, - hard, - ]; - - /// for unique solution, the [blanks] should be equal or less than 54 - const GameModeSudoku._({ - required super.name, - required this.blanks, - this.enableFillerHint = false, - }) : assert(blanks <= 54); - - factory GameModeSudoku.fromJson(String name) => name2mode[name] ?? easy; - - @override - bool operator ==(Object other) { - return other is GameModeSudoku && runtimeType == other.runtimeType && name == other.name && blanks == other.blanks; - } - - @override - int get hashCode => Object.hash(name, blanks); - - @override - String l10n() => "game.sudoku.gameMode.$name".tr(); -} diff --git a/lib/game/sudoku/entity/note.dart b/lib/game/sudoku/entity/note.dart deleted file mode 100644 index 0038b3b24..000000000 --- a/lib/game/sudoku/entity/note.dart +++ /dev/null @@ -1,40 +0,0 @@ -extension type const SudokuCellNote(int _notes) { - const SudokuCellNote.empty() : _notes = 0; - - factory SudokuCellNote.fromJson(dynamic json) { - return SudokuCellNote((json as num).toInt()); - } - - int toJson() => _notes; - - /// Setter for a specific note (1-9) - SudokuCellNote setNoted(int number, bool value) { - if (number < 1 || number > 9) { - throw ArgumentError("Invalid note number. Must be between 1 and 9."); - } - - // Calculate bit position for the note (0 for 1st bit, 1 for 2nd, etc.) - final int bitPosition = number - 1; - - // Update the bitmask based on the value - final newNotes = value ? (_notes | (1 << bitPosition)) : (_notes & ~(1 << bitPosition)); - - // Create a new instance with the updated bitmask - return SudokuCellNote(newNotes); - } - - /// Getter for a specific note (1-9) - bool getNoted(int number) { - if (number < 1 || number > 9) { - throw ArgumentError("Invalid note number. Must be between 1 and 9."); - } - - final int bitPosition = number - 1; - return (_notes & (1 << bitPosition)) != 0; - } - - bool get anyNoted { - // 0b111111111 - return (_notes & 0x1FF) != 0; - } -} diff --git a/lib/game/sudoku/entity/pref.dart b/lib/game/sudoku/entity/pref.dart deleted file mode 100644 index 62253f7b8..000000000 --- a/lib/game/sudoku/entity/pref.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:copy_with_extension/copy_with_extension.dart'; -import 'package:json_annotation/json_annotation.dart'; - -import 'mode.dart'; - -part "pref.g.dart"; - -@JsonSerializable() -@CopyWith(skipFields: true) -class GamePrefSudoku { - final GameModeSudoku mode; - - const GamePrefSudoku({ - this.mode = GameModeSudoku.easy, - }); - - @override - bool operator ==(Object other) { - return other is GamePrefSudoku && runtimeType == other.runtimeType && mode == other.mode; - } - - @override - int get hashCode => Object.hash(mode, 0); - - factory GamePrefSudoku.fromJson(Map json) => _$GamePrefSudokuFromJson(json); - - Map toJson() => _$GamePrefSudokuToJson(this); -} diff --git a/lib/game/sudoku/entity/pref.g.dart b/lib/game/sudoku/entity/pref.g.dart deleted file mode 100644 index cee0a0dfb..000000000 --- a/lib/game/sudoku/entity/pref.g.dart +++ /dev/null @@ -1,63 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'pref.dart'; - -// ************************************************************************** -// CopyWithGenerator -// ************************************************************************** - -abstract class _$GamePrefSudokuCWProxy { - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// GamePrefSudoku(...).copyWith(id: 12, name: "My name") - /// ```` - GamePrefSudoku call({ - GameModeSudoku? mode, - }); -} - -/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfGamePrefSudoku.copyWith(...)`. -class _$GamePrefSudokuCWProxyImpl implements _$GamePrefSudokuCWProxy { - const _$GamePrefSudokuCWProxyImpl(this._value); - - final GamePrefSudoku _value; - - @override - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// GamePrefSudoku(...).copyWith(id: 12, name: "My name") - /// ```` - GamePrefSudoku call({ - Object? mode = const $CopyWithPlaceholder(), - }) { - return GamePrefSudoku( - mode: mode == const $CopyWithPlaceholder() || mode == null - ? _value.mode - // ignore: cast_nullable_to_non_nullable - : mode as GameModeSudoku, - ); - } -} - -extension $GamePrefSudokuCopyWith on GamePrefSudoku { - /// Returns a callable class that can be used as follows: `instanceOfGamePrefSudoku.copyWith(...)`. - // ignore: library_private_types_in_public_api - _$GamePrefSudokuCWProxy get copyWith => _$GamePrefSudokuCWProxyImpl(this); -} - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -GamePrefSudoku _$GamePrefSudokuFromJson(Map json) => GamePrefSudoku( - mode: json['mode'] == null ? GameModeSudoku.easy : GameModeSudoku.fromJson(json['mode'] as String), - ); - -Map _$GamePrefSudokuToJson(GamePrefSudoku instance) => { - 'mode': instance.mode, - }; diff --git a/lib/game/sudoku/entity/record.dart b/lib/game/sudoku/entity/record.dart deleted file mode 100644 index 35b33a70b..000000000 --- a/lib/game/sudoku/entity/record.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/game/entity/game_result.dart'; -import 'package:mimir/game/entity/record.dart'; -import 'package:mimir/game/sudoku/entity/board.dart'; -import 'package:uuid/uuid.dart'; - -import 'blueprint.dart'; -import 'mode.dart'; - -part "record.g.dart"; - -@JsonSerializable() -class RecordSudoku extends GameRecord { - final GameResult result; - final Duration playtime; - final GameModeSudoku mode; - final int blanks; - final String blueprint; - - RecordSudoku({ - required super.uuid, - required super.ts, - required this.result, - required this.playtime, - required this.mode, - required this.blueprint, - }) : blanks = mode.blanks; - - factory RecordSudoku.createFrom({ - required SudokuBoard board, - required Duration playtime, - required GameModeSudoku mode, - required GameResult result, - }) { - final blueprint = BlueprintSudoku( - board: board, - mode: mode, - ); - return RecordSudoku( - uuid: const Uuid().v4(), - ts: DateTime.now(), - result: result, - mode: mode, - playtime: playtime, - blueprint: blueprint.build(), - ); - } - - Map toJson() => _$RecordSudokuToJson(this); - - factory RecordSudoku.fromJson(Map json) => _$RecordSudokuFromJson(json); -} diff --git a/lib/game/sudoku/entity/record.g.dart b/lib/game/sudoku/entity/record.g.dart deleted file mode 100644 index 0929c165d..000000000 --- a/lib/game/sudoku/entity/record.g.dart +++ /dev/null @@ -1,30 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'record.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -RecordSudoku _$RecordSudokuFromJson(Map json) => RecordSudoku( - uuid: json['uuid'] as String? ?? genUuidV4(), - ts: DateTime.parse(json['ts'] as String), - result: $enumDecode(_$GameResultEnumMap, json['result']), - playtime: Duration(microseconds: (json['playtime'] as num).toInt()), - mode: GameModeSudoku.fromJson(json['mode'] as String), - blueprint: json['blueprint'] as String, - ); - -Map _$RecordSudokuToJson(RecordSudoku instance) => { - 'uuid': instance.uuid, - 'ts': instance.ts.toIso8601String(), - 'result': _$GameResultEnumMap[instance.result]!, - 'playtime': instance.playtime.inMicroseconds, - 'mode': instance.mode, - 'blueprint': instance.blueprint, - }; - -const _$GameResultEnumMap = { - GameResult.victory: 'victory', - GameResult.gameOver: 'gameOver', -}; diff --git a/lib/game/sudoku/entity/save.dart b/lib/game/sudoku/entity/save.dart deleted file mode 100644 index 96a700fee..000000000 --- a/lib/game/sudoku/entity/save.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:mimir/game/storage/save.dart'; -import 'package:mimir/storage/hive/init.dart'; -import 'package:mimir/utils/list2d/list2d.dart'; - -import 'mode.dart'; -import 'board.dart'; -import 'note.dart'; - -part "save.g.dart"; - -List2D _defaultCells() { - return List2D.generate( - sudokuSides, - sudokuSides, - (row, column, index) => const Cell4Save(), - ); -} - -@JsonSerializable() -class Cell4Save { - final int userInput; - final int correctValue; - - const Cell4Save({ - this.userInput = SudokuCell.disableInputNumber, - this.correctValue = SudokuCell.emptyInputNumber, - }); - - Map toJson() => _$Cell4SaveToJson(this); - - factory Cell4Save.fromJson(Map json) => _$Cell4SaveFromJson(json); -} - -List _defaultNotes() { - return List.generate(sudokuSides * sudokuSides, (index) => const SudokuCellNote.empty()); -} - -@JsonSerializable() -class SaveSudoku { - @JsonKey(defaultValue: _defaultCells) - final List2D cells; - final Duration playtime; - final GameModeSudoku mode; - @JsonKey(defaultValue: _defaultNotes) - final List notes; - - const SaveSudoku({ - required this.cells, - this.playtime = Duration.zero, - this.mode = GameModeSudoku.easy, - required this.notes, - }); - - Map toJson() => _$SaveSudokuToJson(this); - - factory SaveSudoku.fromJson(Map json) => _$SaveSudokuFromJson(json); -} diff --git a/lib/game/sudoku/entity/save.g.dart b/lib/game/sudoku/entity/save.g.dart deleted file mode 100644 index 73f5a03ed..000000000 --- a/lib/game/sudoku/entity/save.g.dart +++ /dev/null @@ -1,36 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'save.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -Cell4Save _$Cell4SaveFromJson(Map json) => Cell4Save( - userInput: (json['userInput'] as num?)?.toInt() ?? SudokuCell.disableInputNumber, - correctValue: (json['correctValue'] as num?)?.toInt() ?? SudokuCell.emptyInputNumber, - ); - -Map _$Cell4SaveToJson(Cell4Save instance) => { - 'userInput': instance.userInput, - 'correctValue': instance.correctValue, - }; - -SaveSudoku _$SaveSudokuFromJson(Map json) => SaveSudoku( - cells: json['cells'] == null - ? _defaultCells() - : List2D.fromJson( - json['cells'] as Map, (value) => Cell4Save.fromJson(value as Map)), - playtime: json['playtime'] == null ? Duration.zero : Duration(microseconds: (json['playtime'] as num).toInt()), - mode: json['mode'] == null ? GameModeSudoku.easy : GameModeSudoku.fromJson(json['mode'] as String), - notes: (json['notes'] as List?)?.map(SudokuCellNote.fromJson).toList() ?? _defaultNotes(), - ); - -Map _$SaveSudokuToJson(SaveSudoku instance) => { - 'cells': instance.cells.toJson( - (value) => value, - ), - 'playtime': instance.playtime.inMicroseconds, - 'mode': instance.mode, - 'notes': instance.notes, - }; diff --git a/lib/game/sudoku/entity/state.dart b/lib/game/sudoku/entity/state.dart deleted file mode 100644 index 472d15485..000000000 --- a/lib/game/sudoku/entity/state.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:copy_with_extension/copy_with_extension.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import 'package:mimir/game/sudoku/entity/save.dart'; - -import 'board.dart'; -import 'mode.dart'; -import 'note.dart'; - -part "state.g.dart"; - -@immutable -@JsonSerializable() -@CopyWith(skipFields: true) -class GameStateSudoku { - final GameStatus status; - final GameModeSudoku mode; - final SudokuBoard board; - final Duration playtime; - final List notes; - - const GameStateSudoku({ - this.status = GameStatus.idle, - required this.mode, - required this.board, - this.playtime = Duration.zero, - required this.notes, - }); - - GameStateSudoku.newGame({ - required this.mode, - required this.board, - }) : status = GameStatus.idle, - playtime = Duration.zero, - notes = List.generate(sudokuSides * sudokuSides, (index) => const SudokuCellNote.empty()); - - GameStateSudoku.byDefault() - : status = GameStatus.idle, - mode = GameModeSudoku.easy, - playtime = Duration.zero, - board = SudokuBoard.byDefault(), - notes = List.generate(sudokuSides * sudokuSides, (index) => const SudokuCellNote.empty()); - - factory GameStateSudoku.fromJson(Map json) => _$GameStateSudokuFromJson(json); - - Map toJson() => _$GameStateSudokuToJson(this); - - factory GameStateSudoku.fromSave(SaveSudoku save) { - final board = SudokuBoard.fromSave(save); - return GameStateSudoku( - mode: save.mode, - board: board, - playtime: save.playtime, - status: GameStatus.running, - notes: save.notes, - ); - } - - SaveSudoku toSave() { - return SaveSudoku( - cells: board.toSave(), - playtime: playtime, - mode: mode, - notes: notes, - ); - } -} diff --git a/lib/game/sudoku/entity/state.g.dart b/lib/game/sudoku/entity/state.g.dart deleted file mode 100644 index 6f0a38a8f..000000000 --- a/lib/game/sudoku/entity/state.g.dart +++ /dev/null @@ -1,102 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'state.dart'; - -// ************************************************************************** -// CopyWithGenerator -// ************************************************************************** - -abstract class _$GameStateSudokuCWProxy { - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// GameStateSudoku(...).copyWith(id: 12, name: "My name") - /// ```` - GameStateSudoku call({ - GameStatus? status, - GameModeSudoku? mode, - SudokuBoard? board, - Duration? playtime, - List? notes, - }); -} - -/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfGameStateSudoku.copyWith(...)`. -class _$GameStateSudokuCWProxyImpl implements _$GameStateSudokuCWProxy { - const _$GameStateSudokuCWProxyImpl(this._value); - - final GameStateSudoku _value; - - @override - - /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. - /// - /// Usage - /// ```dart - /// GameStateSudoku(...).copyWith(id: 12, name: "My name") - /// ```` - GameStateSudoku call({ - Object? status = const $CopyWithPlaceholder(), - Object? mode = const $CopyWithPlaceholder(), - Object? board = const $CopyWithPlaceholder(), - Object? playtime = const $CopyWithPlaceholder(), - Object? notes = const $CopyWithPlaceholder(), - }) { - return GameStateSudoku( - status: status == const $CopyWithPlaceholder() || status == null - ? _value.status - // ignore: cast_nullable_to_non_nullable - : status as GameStatus, - mode: mode == const $CopyWithPlaceholder() || mode == null - ? _value.mode - // ignore: cast_nullable_to_non_nullable - : mode as GameModeSudoku, - board: board == const $CopyWithPlaceholder() || board == null - ? _value.board - // ignore: cast_nullable_to_non_nullable - : board as SudokuBoard, - playtime: playtime == const $CopyWithPlaceholder() || playtime == null - ? _value.playtime - // ignore: cast_nullable_to_non_nullable - : playtime as Duration, - notes: notes == const $CopyWithPlaceholder() || notes == null - ? _value.notes - // ignore: cast_nullable_to_non_nullable - : notes as List, - ); - } -} - -extension $GameStateSudokuCopyWith on GameStateSudoku { - /// Returns a callable class that can be used as follows: `instanceOfGameStateSudoku.copyWith(...)`. - // ignore: library_private_types_in_public_api - _$GameStateSudokuCWProxy get copyWith => _$GameStateSudokuCWProxyImpl(this); -} - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -GameStateSudoku _$GameStateSudokuFromJson(Map json) => GameStateSudoku( - status: $enumDecodeNullable(_$GameStatusEnumMap, json['status']) ?? GameStatus.idle, - mode: GameModeSudoku.fromJson(json['mode'] as String), - board: SudokuBoard.fromJson(json['board']), - playtime: json['playtime'] == null ? Duration.zero : Duration(microseconds: (json['playtime'] as num).toInt()), - notes: (json['notes'] as List).map(SudokuCellNote.fromJson).toList(), - ); - -Map _$GameStateSudokuToJson(GameStateSudoku instance) => { - 'status': _$GameStatusEnumMap[instance.status]!, - 'mode': instance.mode, - 'board': instance.board, - 'playtime': instance.playtime.inMicroseconds, - 'notes': instance.notes, - }; - -const _$GameStatusEnumMap = { - GameStatus.running: 'running', - GameStatus.idle: 'idle', - GameStatus.gameOver: 'gameOver', - GameStatus.victory: 'victory', -}; diff --git a/lib/game/sudoku/i18n.dart b/lib/game/sudoku/i18n.dart deleted file mode 100644 index 657124e7b..000000000 --- a/lib/game/sudoku/i18n.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:mimir/game/i18n.dart'; -import 'package:mimir/l10n/common.dart'; - -import 'entity/mode.dart'; -import 'r.dart'; - -const i18n = _I18n(); - -class _I18n with CommonI18nMixin, CommonGameI18nMixin { - const _I18n(); - - static const ns = "game.${RSudoku.name}"; - final records = const _Records(); - - String get title => "$ns.title".tr(); -} - -class _Records { - static const ns = "${_I18n.ns}.records"; - - const _Records(); - - String get title => "$ns.title".tr(); - - String record({ - required GameModeSudoku mode, - required int blanks, - }) => - "$ns.record".tr(namedArgs: { - "mode": mode.l10n(), - "blanks": "$blanks", - }); -} diff --git a/lib/game/sudoku/main.dart b/lib/game/sudoku/main.dart deleted file mode 100644 index 72a498e82..000000000 --- a/lib/game/sudoku/main.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:flutter/cupertino.dart'; - -import 'page/index.dart'; - -void main() { - WidgetsFlutterBinding.ensureInitialized(); - runApp(const GameSudokuPage()); -} diff --git a/lib/game/sudoku/manager/logic.dart b/lib/game/sudoku/manager/logic.dart deleted file mode 100644 index 2350a46eb..000000000 --- a/lib/game/sudoku/manager/logic.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mimir/game/entity/game_result.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import 'package:mimir/game/sudoku/entity/state.dart'; -import 'package:mimir/game/utils.dart'; - -import '../entity/mode.dart'; -import '../entity/note.dart'; -import '../entity/board.dart'; -import '../entity/record.dart'; -import '../entity/save.dart'; -import '../storage.dart'; - -class GameLogic extends StateNotifier { - GameLogic([GameStateSudoku? initial]) : super(initial ?? GameStateSudoku.byDefault()); - - void initGame({required GameModeSudoku gameMode}) { - final board = SudokuBoard.generate(emptySquares: gameMode.blanks); - state = GameStateSudoku.newGame(mode: gameMode, board: board); - } - - void startGame() { - state = state.copyWith(status: GameStatus.running); - } - - void fromSave(SaveSudoku save) { - state = GameStateSudoku.fromSave(save); - } - - Duration get playtime => state.playtime; - - set playtime(Duration playtime) => state = state.copyWith( - playtime: playtime, - ); - - Future save() async { - if (state.status.shouldSave) { - await StorageSudoku.save.save(state.toSave()); - } else { - await StorageSudoku.save.delete(); - } - } - - void setNoted(int cellIndex, int number, bool noted) { - final oldNotes = state.notes; - if (oldNotes[cellIndex].getNoted(number) == noted) return; - final newNotes = List.of(oldNotes)..[cellIndex] = oldNotes[cellIndex].setNoted(number, noted); - state = state.copyWith( - notes: newNotes, - ); - } - - /// Clear both note and filled number. - void clearCell(int cellIndex) { - clearNote(cellIndex); - clearFilledCell(cellIndex); - } - - void clearNote(int cellIndex) { - final oldNotes = state.notes; - if (!oldNotes[cellIndex].anyNoted) return; - final newNotes = List.of(oldNotes)..[cellIndex] = const SudokuCellNote.empty(); - state = state.copyWith( - notes: newNotes, - ); - } - - bool getNoted(int cellIndex, int number) { - return state.notes[cellIndex].getNoted(number); - } - - void fillCell(int cellIndex, int number) { - final oldBoard = state.board; - final oldCell = oldBoard.getCellByIndex(cellIndex); - number = oldCell.userInput == number ? SudokuCell.emptyInputNumber : number; - final newBoard = oldBoard.changeCell(cellIndex, number); - state = state.copyWith( - board: newBoard, - ); - checkWin(); - } - - void clearFilledCell(int cellIndex) { - final oldBoard = state.board; - final newBoard = oldBoard.changeCell(cellIndex, SudokuCell.emptyInputNumber); - state = state.copyWith( - board: newBoard, - ); - } - - int getFilled(int cellIndex) { - return state.board.getCellByIndex(cellIndex).userInput; - } - - void checkWin() { - if (state.board.isSolved) { - onVictory(); - } - } - - void onVictory() { - state = state.copyWith( - status: GameStatus.victory, - ); - StorageSudoku.record.add(RecordSudoku.createFrom( - board: state.board, - playtime: state.playtime, - mode: state.mode, - result: GameResult.victory, - )); - } - - void onGameOver() { - state = state.copyWith( - status: GameStatus.gameOver, - ); - StorageSudoku.record.add(RecordSudoku.createFrom( - board: state.board, - playtime: state.playtime, - mode: state.mode, - result: GameResult.gameOver, - )); - applyGameHapticFeedback(HapticFeedbackIntensity.heavy); - } -} diff --git a/lib/game/sudoku/page/game.dart b/lib/game/sudoku/page/game.dart deleted file mode 100644 index af5a7b69e..000000000 --- a/lib/game/sudoku/page/game.dart +++ /dev/null @@ -1,329 +0,0 @@ -// Thanks to "https://github.com/einsitang/sudoku-flutter" -// LICENSE: https://github.com/einsitang/sudoku-flutter/blob/fc31c063d84ba272bf30219ea08724272167b8ef/LICENSE -// Modifications copyright©️2024 Plum Technology Ltd. - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mimir/design/adaptive/multiplatform.dart'; -import 'package:mimir/game/ability/ability.dart'; -import 'package:mimir/game/ability/autosave.dart'; -import 'package:mimir/game/ability/timer.dart'; -import 'package:mimir/game/entity/game_status.dart'; -import 'package:mimir/game/entity/timer.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/game/widget/party_popper.dart'; -import 'package:mimir/utils/keyboard.dart'; - -import '../entity/state.dart'; -import '../entity/note.dart'; -import '../entity/board.dart'; -import '../manager/logic.dart'; -import '../settings.dart'; -import '../storage.dart'; -import '../widget/cell.dart'; -import '../widget/hud.dart'; -import '../widget/keyboard.dart'; -import '../widget/modal.dart'; -import '../widget/tool.dart'; -import "../i18n.dart"; - -final stateSudoku = StateNotifierProvider.autoDispose((ref) { - return GameLogic(); -}); - -class GameSudoku extends ConsumerStatefulWidget { - final bool newGame; - - const GameSudoku({ - super.key, - this.newGame = true, - }); - - @override - ConsumerState createState() => _GameSudokuState(); -} - -class _GameSudokuState extends ConsumerState with WidgetsBindingObserver, GameWidgetAbilityMixin { - int selectedCellIndex = 0; - bool enableNoting = false; - final $focusNode = FocusNode(); - late TimerWidgetAbility timerAbility; - - GameTimer get timer => timerAbility.timer; - - @override - List createAbility() => [ - AutoSaveWidgetAbility(onSave: onSave), - timerAbility = TimerWidgetAbility(), - ]; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.endOfFrame.then((_) { - timer.addListener((state) { - ref.read(stateSudoku.notifier).playtime = state; - }); - final logic = ref.read(stateSudoku.notifier); - if (widget.newGame) { - logic.initGame(gameMode: SettingsSudoku.$.pref.mode); - logic.startGame(); - } else { - final save = StorageSudoku.save.load(); - if (save != null) { - logic.fromSave(save); - timer.state = ref.read(stateSudoku).playtime; - } else { - logic.initGame(gameMode: SettingsSudoku.$.pref.mode); - timer.state = ref.read(stateSudoku).playtime; - } - } - }); - } - - @override - void dispose() { - $focusNode.dispose(); - super.dispose(); - } - - void resetGame() { - timer.reset(); - final logic = ref.read(stateSudoku.notifier); - logic.initGame(gameMode: ref.read(stateSudoku).mode); - logic.startGame(); - } - - void onGameStateChange(GameStateSudoku? former, GameStateSudoku current) { - switch (current.status) { - case GameStatus.running: - if (!timer.timerStart) timer.startTimer(); - break; - case GameStatus.idle: - case GameStatus.gameOver: - case GameStatus.victory: - if (timer.timerStart) timer.stopTimer(); - break; - } - } - - @override - Widget build(BuildContext context) { - final gameStatus = ref.watch(stateSudoku.select((state) => state.status)); - ref.listen(stateSudoku, onGameStateChange); - return KeyboardListener( - focusNode: $focusNode, - onKeyEvent: onKey, - autofocus: true, - child: Scaffold( - appBar: AppBar( - title: i18n.title.text(), - actions: [ - IconButton( - icon: Icon(context.icons.refresh), - onPressed: resetGame, - ) - ], - ), - body: [ - buildBody(), - if (gameStatus == GameStatus.victory) - VictoryModal(resetGame: resetGame) - else if (gameStatus == GameStatus.gameOver) - GameOverModal(resetGame: resetGame), - VictoryPartyPopper( - pop: gameStatus == GameStatus.victory, - ), - ].stack(), - ), - ); - } - - void onKey(KeyEvent e) { - if (e is! KeyDownEvent) return; - final number = e.tryGetNumber(); - if (number != null) { - onNumberKeyDown(number); - return; - } - final arrow = e.tryGetArrowKey(); - if (arrow != null) { - onArrowKeyDown(arrow, shiftOn: HardwareKeyboard.instance.isShiftPressed); - } - if (e.logicalKey == LogicalKeyboardKey.delete || e.logicalKey == LogicalKeyboardKey.backspace) { - onDeleteKeyDown(); - } - if (e.character?.toLowerCase() == "n") { - setState(() { - enableNoting = !enableNoting; - }); - } - } - - void onDeleteKeyDown() { - clearSelected(); - } - - void onNumberKeyDown(int number) { - if (!(1 <= number && number <= 9)) return; - final board = ref.read(stateSudoku).board; - final selectedCell = board.getCellByIndex(selectedCellIndex); - if (!selectedCell.canUserInput) return; - fillNumber(number); - } - - void onArrowKeyDown(AxisDirection dir, {required bool shiftOn}) { - final board = ref.read(stateSudoku).board; - final index = board.findNextCell(selectedCellIndex, dir, ignorePuzzle: shiftOn); - if (index == null) return; - setState(() { - selectedCellIndex = index; - }); - } - - Widget buildBody() { - if (context.isPortrait) { - return ListView( - children: [ - const GameHud().padAll(8), - buildCellArea().padAll(4), - buildToolBar(), - buildKeyboard(), - ], - ); - } else { - return [ - buildCellArea().padAll(4).expanded(), - ListView( - children: [ - const GameHud().padAll(8), - buildKeyboard(), - buildToolBar(), - ], - ).expanded(), - ].row(); - } - } - - Widget buildCellArea() { - final notes = ref.watch(stateSudoku.select((state) => state.notes)); - final board = ref.watch(stateSudoku.select((state) => state.board)); - return SudokuCellArea( - itemBuilder: (context, index) => buildCell(index, board, notes), - ); - } - - Widget buildCell( - int index, - SudokuBoard board, - List notes, - ) { - final note = notes[index]; - final selected = selectedCellIndex == index; - final cell = board.getCellByIndex(index); - final zone = board.getZoneWhereCell(cell); - return InkWell( - onTap: () { - setState(() { - HapticFeedback.selectionClick(); - selectedCellIndex = index; - }); - }, - child: CellWidget( - cell: cell, - zone: zone, - board: board, - selectedIndex: selectedCellIndex, - child: note.anyNoted - ? CellNotes( - note: note, - cellSelected: selected, - ) - : CellNumber( - cell: cell, - cellSelected: selected, - ), - ), - ); - } - - Widget buildKeyboard() { - final board = ref.watch(stateSudoku.select((state) => state.board)); - final gameMode = ref.watch(stateSudoku.select((state) => state.mode)); - final gameStatus = ref.watch(stateSudoku.select((state) => state.status)); - return ExcludeFocus( - child: SudokuNumberKeyboard( - board: board, - selectedIndex: selectedCellIndex, - enableFillerHint: gameMode.enableFillerHint, - getOnNumberTap: gameStatus.canPlay - ? (int number) { - return board.canFill(number: number, cellIndex: selectedCellIndex) - ? () { - fillNumber(number); - } - : null; - } - : null, - ), - ); - } - - Widget buildToolBar() { - final selectedCell = ref.watch(stateSudoku.select((state) => state.board)).getCellByIndex(selectedCellIndex); - return [ - ClearNumberButton( - onTap: selectedCell.canUserInput ? clearSelected : null, - ), - // HintButton( - // onTap: _state.hint > 0 ? hint : null, - // ), - NoteNumberButton( - enabled: enableNoting, - onChanged: (newV) { - setState(() { - enableNoting = !enableNoting; - }); - }, - ), - ].row(maa: MainAxisAlignment.spaceEvenly); - } - - void fillNumber(int number) async { - final state = ref.read(stateSudoku); - final gameStatus = state.status; - if (!gameStatus.canPlay) return; - final cellIndex = selectedCellIndex; - final cell = state.board.getCellByIndex(cellIndex); - if (!cell.canUserInput) return; - final logic = ref.read(stateSudoku.notifier); - // Take note or take off - if (enableNoting) { - final isNoted = logic.getNoted(cellIndex, number); - logic.setNoted(cellIndex, number, !isNoted); - return; - } else { - logic.clearNote(cellIndex); - } - // Fill the number or toggle the number - logic.fillCell(cellIndex, number); - } - - void clearSelected() { - final state = ref.read(stateSudoku); - final gameStatus = state.status; - if (!gameStatus.canPlay) return; - final cellIndex = selectedCellIndex; - final cell = state.board.getCellByIndex(cellIndex); - if (!cell.canUserInput) return; - final logic = ref.read(stateSudoku.notifier); - logic.clearCell(cellIndex); - } - - void hint() {} - - void onSave() { - ref.read(stateSudoku.notifier).save(); - } -} diff --git a/lib/game/sudoku/page/index.dart b/lib/game/sudoku/page/index.dart deleted file mode 100644 index e072b34ca..000000000 --- a/lib/game/sudoku/page/index.dart +++ /dev/null @@ -1,24 +0,0 @@ -// Thanks to "https://github.com/VarunS2002/Flutter-Sudoku", -// and "https://github.com/einsitang/sudoku-flutter" -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import 'game.dart'; - -class GameSudokuPage extends StatelessWidget { - final bool newGame; - - const GameSudokuPage({ - super.key, - this.newGame = true, - }); - - @override - Widget build(BuildContext context) { - return ProviderScope( - child: GameSudoku( - newGame: newGame, - ), - ); - } -} diff --git a/lib/game/sudoku/page/records.dart b/lib/game/sudoku/page/records.dart deleted file mode 100644 index 9bdc1b2a5..000000000 --- a/lib/game/sudoku/page/records.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/adaptive/foundation.dart'; -import 'package:mimir/design/adaptive/menu.dart'; -import 'package:mimir/design/adaptive/multiplatform.dart'; -import 'package:mimir/game/entity/game_result.dart'; -import 'package:mimir/game/page/records.dart'; -import 'package:mimir/l10n/extension.dart'; -import 'package:mimir/intent/qrcode/page/view.dart'; - -import '../qrcode/blueprint.dart'; -import '../storage.dart'; -import '../entity/record.dart'; -import '../i18n.dart'; - -class RecordsSudokuPage extends ConsumerStatefulWidget { - const RecordsSudokuPage({super.key}); - - @override - ConsumerState createState() => _RecordsSudokuPageState(); -} - -class _RecordsSudokuPageState extends ConsumerState { - @override - Widget build(BuildContext context) { - return GameRecordsPage( - title: i18n.records.title, - recordStorage: StorageSudoku.record, - itemBuilder: (context, record) { - return RecordSudokuTile(record: record); - }, - ); - } -} - -class RecordSudokuTile extends StatelessWidget { - final RecordSudoku record; - - const RecordSudokuTile({ - super.key, - required this.record, - }); - - @override - Widget build(BuildContext context) { - return ListTile( - leading: Icon( - record.result == GameResult.victory ? Icons.check : Icons.clear, - color: record.result == GameResult.victory ? Colors.green : Colors.red, - ), - title: i18n.records - .record( - mode: record.mode, - blanks: record.blanks, - ) - .text(), - trailing: buildMoreActions(context), - subtitle: [ - context.formatYmdhmsNum(record.ts).text(), - i18n.formatPlaytime(record.playtime).text(), - // record.blueprint.text(), - ].column(caa: CrossAxisAlignment.start), - ); - } - - Widget buildMoreActions(BuildContext context) { - return PullDownMenuButton( - itemBuilder: (ctx) => [ - PullDownItem( - icon: context.icons.refresh, - title: i18n.tryAgain, - onTap: () async { - await onHandleBlueprintSudoku( - context: context, - blueprint: record.blueprint, - ); - }), - PullDownItem( - icon: context.icons.qrcode, - title: i18n.shareQrCode, - onTap: () { - final qrCodeData = blueprintSudokuDeepLink.encodeString( - record.blueprint, - ); - context.showSheet( - (context) => QrCodePage( - title: i18n.title, - data: qrCodeData.toString(), - ), - ); - }, - ), - ], - ); - } -} diff --git a/lib/game/sudoku/qrcode/blueprint.dart b/lib/game/sudoku/qrcode/blueprint.dart deleted file mode 100644 index 1dd61c5df..000000000 --- a/lib/game/sudoku/qrcode/blueprint.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:go_router/go_router.dart'; -import 'package:mimir/game/deep_link/blueprint.dart'; -import 'package:mimir/game/sudoku/r.dart'; - -import '../entity/blueprint.dart'; -import '../storage.dart'; - -const blueprintSudokuDeepLink = GameBlueprintDeepLink( - RSudoku.name, - onHandleBlueprintSudoku, -); - -Future onHandleBlueprintSudoku({ - required BuildContext context, - required String blueprint, -}) async { - final blueprintObj = BlueprintSudoku.from(blueprint); - final state = blueprintObj.create(); - StorageSudoku.save.save(state.toSave()); - context.push("/game/sudoku?continue"); -} diff --git a/lib/game/sudoku/r.dart b/lib/game/sudoku/r.dart deleted file mode 100644 index 75e1f3b4f..000000000 --- a/lib/game/sudoku/r.dart +++ /dev/null @@ -1,4 +0,0 @@ -class RSudoku { - static const name = "sudoku"; - static const version = 1; -} diff --git a/lib/game/sudoku/settings.dart b/lib/game/sudoku/settings.dart deleted file mode 100644 index 6546c7ad0..000000000 --- a/lib/game/sudoku/settings.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:hive/hive.dart'; -import 'package:mimir/storage/hive/init.dart'; -import 'package:mimir/utils/hive.dart'; -import 'package:mimir/utils/json.dart'; - -import 'entity/pref.dart'; - -class _K { - static const ns = "/settings"; - static const pref = '$ns/pref'; -} - -class SettingsSudoku { - static final $ = SettingsSudoku._(); - - Box get box => HiveInit.gameSudoku; - - SettingsSudoku._(); - - /// [false] by default. - GamePrefSudoku get pref => - decodeJsonObject(box.safeGet(_K.pref), (obj) => GamePrefSudoku.fromJson(obj)) ?? const GamePrefSudoku(); - - set pref(GamePrefSudoku newV) => box.safePut(_K.pref, encodeJsonObject(newV)); - - late final $pref = box.providerWithDefault( - _K.pref, - get: () => pref, - set: (v) => pref = v, - () => const GamePrefSudoku(), - ); -} diff --git a/lib/game/sudoku/storage.dart b/lib/game/sudoku/storage.dart deleted file mode 100644 index 9b200639e..000000000 --- a/lib/game/sudoku/storage.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:mimir/game/storage/record.dart'; -import 'package:mimir/game/storage/save.dart'; -import 'package:mimir/storage/hive/init.dart'; - -import 'entity/record.dart'; -import 'entity/save.dart'; -import 'r.dart'; - -class StorageSudoku { - static const _ns = "/${RSudoku.name}/${RSudoku.version}"; - static final save = GameSaveStorage( - () => HiveInit.gameSudoku, - prefix: _ns, - serialize: (save) => save.toJson(), - deserialize: SaveSudoku.fromJson, - ); - static final record = GameRecordStorage( - () => HiveInit.gameSudoku, - prefix: _ns, - serialize: (record) => record.toJson(), - deserialize: RecordSudoku.fromJson, - ); -} diff --git a/lib/game/sudoku/utils.dart b/lib/game/sudoku/utils.dart deleted file mode 100644 index 376828c38..000000000 --- a/lib/game/sudoku/utils.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/game/sudoku/entity/board.dart'; - -Color getCellBgColor({ - required BuildContext context, - required SudokuBoard board, - required SudokuBoardZone zone, - required SudokuCell cell, - required int selectedIndex, -}) { - final index = cell.index; - // current cell is selected - if (index == selectedIndex) { - return context.colorScheme.primaryContainer; - } - if (isRelated(index, selectedIndex)) { - return context.colorScheme.secondaryContainer; - } - final selectedCell = board.getCellByIndex(selectedIndex); - if (isTheSameNumber(cell, selectedCell)) { - return context.colorScheme.tertiaryContainer; - } - - // return context.colorScheme.surface; - return zone.zoneIndex.isOdd - ? context.colorScheme.surface - : context.colorScheme.surfaceContainerHighest.withOpacity(0.5); -} - -bool isRelated(int indexA, int indexB) { - return SudokuBoardZone.getZoneIndexByIndex(indexA) == SudokuBoardZone.getZoneIndexByIndex(indexB) || - _isTheSameRow(indexA, indexB) || - _isTheSameColumn(indexA, indexB); -} - -bool _isTheSameRow(int indexA, int indexB) => SudokuBoard.getRowFrom(indexA) == SudokuBoard.getRowFrom(indexB); - -bool _isTheSameColumn(int indexA, int indexB) => SudokuBoard.getColumnFrom(indexA) == SudokuBoard.getColumnFrom(indexB); - -bool isTheSameNumber(SudokuCell a, SudokuCell b) { - if (a.canUserInput && a.emptyInput) return false; - if (b.canUserInput && b.emptyInput) return false; - if (a.canUserInput && b.canUserInput) { - return a.userInput == b.userInput; - } - if (!a.canUserInput && !b.canUserInput) { - return a.correctValue == b.correctValue; - } - final puzzle = !a.canUserInput ? a : b; - final need2Fill = puzzle == a ? b : a; - return need2Fill.userInput == puzzle.correctValue; -} diff --git a/lib/game/sudoku/widget/cell.dart b/lib/game/sudoku/widget/cell.dart deleted file mode 100644 index 8bff8b6a2..000000000 --- a/lib/game/sudoku/widget/cell.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/utils/list2d/edge.dart'; - -import '../entity/board.dart'; -import '../entity/note.dart'; -import '../utils.dart'; - -class CellNotes extends StatelessWidget { - final SudokuCellNote note; - final bool cellSelected; - - const CellNotes({ - super.key, - required this.note, - this.cellSelected = false, - }); - - @override - Widget build(BuildContext context) { - return GridView.builder( - padding: EdgeInsets.zero, - physics: const NeverScrollableScrollPhysics(), - itemCount: 9, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), - itemBuilder: (context, index) { - final number = index + 1; - final notedThis = note.getNoted(number); - return Text( - notedThis ? "$number" : "", - textAlign: TextAlign.center, - style: context.textTheme.labelSmall?.copyWith( - color: cellSelected ? context.colorScheme.onPrimaryContainer : context.colorScheme.onSurfaceVariant, - ), - ); - }, - ); - } -} - -class CellNumber extends StatelessWidget { - final SudokuCell cell; - final bool cellSelected; - - const CellNumber({ - super.key, - required this.cell, - this.cellSelected = false, - }); - - @override - Widget build(BuildContext context) { - return Text( - cell.isPuzzle ? "${cell.correctValue}" : (cell.emptyInput ? "" : "${cell.userInput}"), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 25, - fontWeight: cell.isPuzzle ? FontWeight.w800 : FontWeight.normal, - color: cell.isPuzzle - ? context.colorScheme.onSurfaceVariant - : cell.isSolved - ? context.colorScheme.onPrimaryContainer - : Colors.red, - ), - ); - } -} - -class CellWidget extends StatelessWidget { - final SudokuCell cell; - final SudokuBoard board; - final SudokuBoardZone zone; - final int selectedIndex; - final Widget child; - - const CellWidget({ - super.key, - required this.cell, - required this.board, - required this.zone, - required this.selectedIndex, - required this.child, - }); - - @override - Widget build(BuildContext context) { - final borderColor = context.colorScheme.onSurface; - const borderRadius = Radius.circular(12); - final edgeAgainstZone = zone.cellOnWhichEdge(cell); - final edgeAgainstBoard = board.cellOnWhichEdge(cell); - var innerWidth = 0.5; - var edgeWidth = 1.5; - const selectionWidth = 3.5; - if (selectedIndex == cell.index) { - edgeWidth = selectionWidth; - innerWidth = selectionWidth; - } - return AnimatedContainer( - duration: Durations.short4, - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: edgeAgainstBoard == null - ? null - : BorderRadius.only( - topLeft: edgeAgainstBoard.topLeft ? borderRadius : Radius.zero, - topRight: edgeAgainstBoard.topRight ? borderRadius : Radius.zero, - bottomLeft: edgeAgainstBoard.bottomLeft ? borderRadius : Radius.zero, - bottomRight: edgeAgainstBoard.bottomRight ? borderRadius : Radius.zero, - ), - color: getCellBgColor( - cell: cell, - board: board, - zone: zone, - selectedIndex: selectedIndex, - context: context, - ), - border: edgeAgainstZone == null - ? Border.all( - color: borderColor, - width: innerWidth, - ) - : Border( - top: BorderSide( - color: borderColor, - width: edgeAgainstZone.top ? edgeWidth : innerWidth, - ), - right: BorderSide( - color: borderColor, - width: edgeAgainstZone.right ? edgeWidth : innerWidth, - ), - bottom: BorderSide( - color: borderColor, - width: edgeAgainstZone.bottom ? edgeWidth : innerWidth, - ), - left: BorderSide( - color: borderColor, - width: edgeAgainstZone.left ? edgeWidth : innerWidth, - ), - ), - ), - child: child, - ); - } -} - -class SudokuCellArea extends StatelessWidget { - final Widget Function(BuildContext context, int index) itemBuilder; - - const SudokuCellArea({ - super.key, - required this.itemBuilder, - }); - - @override - Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, box) { - return [ - for (var row = 0; row < 9; row++) - [ - for (var column = 0; column < 9; column++) - itemBuilder( - context, - row * 9 + column, - ).sizedAll(min(box.maxWidth / 9, box.maxHeight / 9)), - ].row(mas: MainAxisSize.min), - ].column(mas: MainAxisSize.min); - }); - } -} diff --git a/lib/game/sudoku/widget/hud.dart b/lib/game/sudoku/widget/hud.dart deleted file mode 100644 index 96bf8e235..000000000 --- a/lib/game/sudoku/widget/hud.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; - -import '../page/game.dart'; -import '../i18n.dart'; - -class GameHudMistake extends StatelessWidget { - final int mistakes; - final int maxMistakes; - - const GameHudMistake({ - super.key, - required this.mistakes, - required this.maxMistakes, - }); - - @override - Widget build(BuildContext context) { - return [ - const Icon(Icons.heart_broken), - Text("$mistakes / $maxMistakes", style: const TextStyle(fontSize: 18)), - ].row( - mas: MainAxisSize.min, - ); - } -} - -class GameHudHint extends StatelessWidget { - final int hintCount; - - const GameHudHint({ - super.key, - required this.hintCount, - }); - - @override - Widget build(BuildContext context) { - return [ - const Icon(Icons.lightbulb), - Text(" x $hintCount", style: const TextStyle(fontSize: 18)), - ].row(mas: MainAxisSize.min); - } -} - -class GameHudTimer extends ConsumerWidget { - const GameHudTimer({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final playTime = ref.watch(stateSudoku.select((state) => state.playtime)); - final gameMode = ref.watch(stateSudoku.select((state) => state.mode)); - return Text( - "${gameMode.l10n()} - ${i18n.formatPlaytime(playTime)}", - ); - } -} - -class GameHud extends ConsumerWidget { - const GameHud({ - super.key, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - const int? mistakeCount = null; - const int? hintCount = null; - return Card.filled( - child: [ - if (mistakeCount != null) - Expanded( - flex: 1, - child: GameHudMistake( - mistakes: mistakeCount, - maxMistakes: 3, - ).align( - at: Alignment.centerLeft, - ), - ), - // indicator - Expanded( - flex: 2, - child: const GameHudTimer().align( - at: Alignment.center, - ), - ), - if (hintCount != null) - Expanded( - flex: 1, - child: GameHudHint(hintCount: hintCount).align( - at: Alignment.centerRight, - ), - ) - ].row().padSymmetric(v: 8, h: 16), - ); - } -} diff --git a/lib/game/sudoku/widget/keyboard.dart b/lib/game/sudoku/widget/keyboard.dart deleted file mode 100644 index 148f0e43a..000000000 --- a/lib/game/sudoku/widget/keyboard.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:rettulf/rettulf.dart'; - -import '../entity/board.dart'; - -class SudokuNumberKey extends StatelessWidget { - final int number; - final VoidCallback? onTap; - final Color? color; - - const SudokuNumberKey({ - super.key, - required this.number, - this.onTap, - this.color, - }); - - @override - Widget build(BuildContext context) { - final onTap = this.onTap; - return InkWell( - onTap: onTap == null - ? null - : () { - HapticFeedback.selectionClick(); - onTap(); - }, - child: Text( - '$number', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 24, - color: onTap == null ? context.theme.disabledColor : color, - fontWeight: FontWeight.bold, - ), - ).padSymmetric(h: 28, v: 16), - ); - } -} - -class SudokuNumberKeyboard extends StatelessWidget { - final int selectedIndex; - final SudokuBoard board; - final bool enableFillerHint; - final VoidCallback? Function(int number)? getOnNumberTap; - - const SudokuNumberKeyboard({ - super.key, - required this.selectedIndex, - required this.board, - this.getOnNumberTap, - this.enableFillerHint = false, - }); - - @override - Widget build(BuildContext context) { - final getOnNumberTap = this.getOnNumberTap; - final relatedCells = board.relatedOf(selectedIndex).toList(); - return _SudokuNumberKeyboardPad( - getNumberColor: (number) => !enableFillerHint - ? null - : relatedCells.any((cell) => cell.canUserInput ? false : cell.correctValue == number) - ? Colors.red - : null, - getOnNumberTap: getOnNumberTap, - ); - } -} - -class _SudokuNumberKeyboardPad extends StatelessWidget { - final Color? Function(int number)? getNumberColor; - final VoidCallback? Function(int number)? getOnNumberTap; - - const _SudokuNumberKeyboardPad({ - this.getNumberColor, - this.getOnNumberTap, - }); - - @override - Widget build(BuildContext context) { - return [ - for (var row = 0; row < 3; row++) - [ - for (var column = 0; column < 3; column++) buildNumber(row, column), - ].row(mas: MainAxisSize.min), - ].column(mas: MainAxisSize.min); - } - - Widget buildNumber(int row, int column) { - final number = row * 3 + column + 1; - return Card.filled( - clipBehavior: Clip.hardEdge, - child: SudokuNumberKey( - color: getNumberColor?.call(number), - number: number, - onTap: getOnNumberTap?.call(number), - ), - ); - } -} diff --git a/lib/game/sudoku/widget/modal.dart b/lib/game/sudoku/widget/modal.dart deleted file mode 100644 index 5c4cb092a..000000000 --- a/lib/game/sudoku/widget/modal.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/game/sudoku/page/game.dart'; -import '../i18n.dart'; - -class GameOverModal extends ConsumerWidget { - final void Function() resetGame; - - const GameOverModal({ - super.key, - required this.resetGame, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Positioned.fill( - child: InkWell( - onTap: () { - resetGame(); - }, - child: Container( - decoration: BoxDecoration( - color: Colors.red[600]!.withOpacity(0.5), - ), - child: Text( - i18n.gameOver, - style: const TextStyle( - fontSize: 64.0, - ), - ).center(), - ), - ), - ); - } -} - -class VictoryModal extends ConsumerWidget { - final void Function() resetGame; - - const VictoryModal({ - super.key, - required this.resetGame, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final playTime = ref.watch(stateSudoku.select((state) => state.playtime)); - return Positioned.fill( - child: Container( - decoration: BoxDecoration( - color: Colors.green[600]!.withOpacity(0.5), - ), - child: MaterialButton( - onPressed: () { - resetGame(); - }, - child: Text( - "${i18n.youWin}\n${i18n.formatPlaytime(playTime)}", - style: const TextStyle( - fontSize: 64.0, - ), - ), - ), - ), - ); - } -} diff --git a/lib/game/sudoku/widget/tool.dart b/lib/game/sudoku/widget/tool.dart deleted file mode 100644 index 60c1092af..000000000 --- a/lib/game/sudoku/widget/tool.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; - -class NoteNumberButton extends StatelessWidget { - final bool enabled; - final ValueChanged onChanged; - - const NoteNumberButton({ - super.key, - required this.enabled, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - return PlatformIconButton( - icon: Icon(enabled ? Icons.note_alt : Icons.note_alt_outlined), - onPressed: () { - onChanged(!enabled); - }, - ); - } -} - -class ClearNumberButton extends StatelessWidget { - final VoidCallback? onTap; - - const ClearNumberButton({ - super.key, - this.onTap, - }); - - @override - Widget build(BuildContext context) { - return PlatformIconButton( - icon: const Icon(Icons.delete), - onPressed: onTap, - ); - } -} - -class HintButton extends StatelessWidget { - final VoidCallback? onTap; - - const HintButton({ - super.key, - this.onTap, - }); - - @override - Widget build(BuildContext context) { - return PlatformIconButton( - icon: const Icon(Icons.lightbulb), - onPressed: onTap, - ); - } -} diff --git a/lib/game/utils.dart b/lib/game/utils.dart deleted file mode 100644 index d329fc55a..000000000 --- a/lib/game/utils.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:mimir/settings/settings.dart'; - -enum HapticFeedbackIntensity { - light, - medium, - heavy, - ; -} - -Future applyGameHapticFeedback([HapticFeedbackIntensity intensity = HapticFeedbackIntensity.light]) async { - if (!Settings.game.enableHapticFeedback) return; - switch (intensity) { - case HapticFeedbackIntensity.light: - await HapticFeedback.lightImpact(); - break; - case HapticFeedbackIntensity.medium: - await HapticFeedback.mediumImpact(); - break; - case HapticFeedbackIntensity.heavy: - await HapticFeedback.heavyImpact(); - // await const Vibration(milliseconds: 200, amplitude: 127).emit(); - break; - } -} diff --git a/lib/game/widget/card.dart b/lib/game/widget/card.dart deleted file mode 100644 index 66be91ba2..000000000 --- a/lib/game/widget/card.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/widget/app.dart'; - -import '../storage/save.dart'; -import '../i18n.dart'; - -class OfflineGameAppCard extends ConsumerWidget { - final String name; - final String baseRoute; - final Widget? view; - final bool supportRecords; - final bool supportLeaderboard; - final GameSaveStorage? save; - - const OfflineGameAppCard({ - super.key, - this.view, - required this.baseRoute, - required this.name, - this.supportRecords = false, - this.supportLeaderboard = false, - this.save, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final save = this.save; - var hasSave = false; - if (save != null) { - hasSave |= ref.watch(save.$saveExistsOf(0)); - } - return AppCard( - title: name.text(), - view: view, - leftActions: [ - if (hasSave != true) - FilledButton( - onPressed: () { - context.push("/game$baseRoute"); - }, - child: i18n.newGame.text(), - ) - else ...[ - FilledButton( - onPressed: () { - context.push("/game$baseRoute?continue"); - }, - child: i18n.continueGame.text(), - ), - FilledButton.tonal( - onPressed: () { - context.push("/game$baseRoute"); - }, - child: i18n.newGame.text(), - ) - ], - ], - rightActions: [ - if (supportRecords) - PlatformIconButton( - onPressed: () { - context.push("/game$baseRoute/records"); - }, - icon: const Icon(Icons.history), - ), - if (supportLeaderboard) - PlatformIconButton( - onPressed: () { - context.push("/game$baseRoute/leaderboard"); - }, - icon: const Icon(Icons.leaderboard), - ), - ], - ); - } -} diff --git a/lib/game/widget/mode.dart b/lib/game/widget/mode.dart deleted file mode 100644 index 263e70cf9..000000000 --- a/lib/game/widget/mode.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/adaptive/multiplatform.dart'; -import 'package:mimir/game/entity/game_mode.dart'; - -import '../i18n.dart'; - -class GameModeSelectorCard extends StatelessWidget { - final List all; - final T current; - final ValueChanged onChanged; - - const GameModeSelectorCard({ - super.key, - required this.all, - required this.current, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - return Card( - child: [ - ListTile( - leading: Icon(context.icons.game), - title: i18n.gameMode.text(), - ), - buildSelector(context), - ] - .column(maa: MainAxisAlignment.spaceEvenly, mas: MainAxisSize.min, caa: CrossAxisAlignment.start) - .padSymmetric(v: 8, h: 16), - ); - } - - Widget buildSelector(BuildContext context) { - return all - .map((mode) => ChoiceChip( - showCheckmark: false, - label: mode.l10n().text(), - selected: mode == current, - onSelected: (v) { - onChanged(mode); - }, - )) - .toList() - .wrap(spacing: 4); - } -} diff --git a/lib/game/widget/party_popper.dart b/lib/game/widget/party_popper.dart deleted file mode 100644 index 20b1fb0e2..000000000 --- a/lib/game/widget/party_popper.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:rettulf/rettulf.dart'; -import 'package:mimir/design/widget/party_popper.dart'; - -class VictoryPartyPopper extends StatefulWidget { - final Duration duration; - final bool pop; - - const VictoryPartyPopper({ - super.key, - this.duration = const Duration(milliseconds: 1500), - this.pop = false, - }); - - @override - State createState() => _VictoryPartyPopperState(); -} - -class _VictoryPartyPopperState extends State with TickerProviderStateMixin { - late AnimationController controller; - - @override - void initState() { - super.initState(); - buildController(); - } - - void buildController() { - controller = AnimationController( - vsync: this, - duration: widget.duration, - ); - controller.addStatusListener((status) { - if (status == AnimationStatus.completed) { - controller.reset(); - } - }); - if (widget.pop) { - controller.forward(); - } - } - - @override - void didUpdateWidget(covariant VictoryPartyPopper oldWidget) { - if (widget.duration != oldWidget.duration) { - controller.dispose(); - buildController(); - } - if (widget.pop != oldWidget.pop) { - if (widget.pop) { - controller.forward(); - } - } - super.didUpdateWidget(oldWidget); - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return [ - buildPopper(PopDirection.forwardX), - buildPopper(PopDirection.backwardX), - ].stack(); - } - - Widget buildPopper(PopDirection dir) { - return PartyPopper( - direction: dir, - motionCurveX: (double t) { - return -t * t / 2 + t; - }.toCurve(), - motionCurveY: (double t) { - return 4 / 3 * t * t - t / 3; - }.toCurve(), - number: 50, - pos: const Offset(-60.0, 30.0), - segmentSize: const Size(15.0, 30.0), - controller: controller, - ); - } -} diff --git a/lib/index.dart b/lib/index.dart index d75279cba..afed02e58 100644 --- a/lib/index.dart +++ b/lib/index.dart @@ -11,7 +11,6 @@ import 'package:mimir/settings/settings.dart'; import 'package:mimir/timetable/i18n.dart' as $timetable; import 'package:mimir/school/i18n.dart' as $school; import 'package:mimir/life/i18n.dart' as $life; -import 'package:mimir/game/i18n.dart' as $game; import 'package:mimir/me/i18n.dart' as $me; import 'package:mimir/timetable/p13n/entity/background.dart'; import 'package:mimir/timetable/p13n/widget/wallpaper.dart'; @@ -89,25 +88,6 @@ class _MainStagePageState extends ConsumerState { label: $life.i18n.navigation, ) ), - // in-app webview only supports Android, and iOS - // if (UniversalPlatform.isAndroid || UniversalPlatform.isIOS) - // ( - // route: "/forum", - // item: ( - // icon: Icons.forum_outlined, - // activeIcon: Icons.forum, - // label: $forum.i18n.navigation, - // ) - // ), - if (ref.watch(Settings.game.$showGameNavigation) && can(AppFeature.game$, ref)) - ( - route: "/game", - item: ( - icon: context.icons.game, - activeIcon: context.icons.gameFilled, - label: $game.i18n.navigation, - ) - ), ( route: "/me", item: ( diff --git a/lib/intent/deep_link/registry.dart b/lib/intent/deep_link/registry.dart index f849fccdc..4a54cee63 100644 --- a/lib/intent/deep_link/registry.dart +++ b/lib/intent/deep_link/registry.dart @@ -1,5 +1,3 @@ -import 'package:mimir/game/minesweeper/qrcode/blueprint.dart'; -import 'package:mimir/game/sudoku/qrcode/blueprint.dart'; import 'package:mimir/r.dart'; import 'package:mimir/settings/deep_link/proxy.dart'; import 'package:mimir/timetable/deep_link/palette.dart'; @@ -19,7 +17,5 @@ class DeepLinkHandlers { const TimetableDeepLink(), const GoRouteDeepLink(), const WebviewDeepLink(), - blueprintMinesweeperDeepLink, - blueprintSudokuDeepLink, ]; } diff --git a/lib/l10n/app.dart b/lib/l10n/app.dart index 88798af39..a049a9153 100644 --- a/lib/l10n/app.dart +++ b/lib/l10n/app.dart @@ -1,7 +1,6 @@ import 'package:mimir/timetable/i18n.dart' as t; import 'package:mimir/school/i18n.dart' as s; import 'package:mimir/life/i18n.dart' as l; -import 'package:mimir/game/i18n.dart' as g; class AppI18n { const AppI18n(); @@ -14,5 +13,4 @@ class _Navigation { String get timetable => t.i18n.navigation; String get school => s.i18n.navigation; String get life => l.i18n.navigation; - String get game => g.i18n.navigation; } diff --git a/lib/route.dart b/lib/route.dart index c9303c2e2..6ade70f73 100644 --- a/lib/route.dart +++ b/lib/route.dart @@ -12,13 +12,6 @@ import 'package:mimir/credentials/entity/user_type.dart'; import 'package:mimir/credentials/init.dart'; import 'package:mimir/feature/feature.dart'; import 'package:mimir/feature/utils.dart'; -import 'package:mimir/game/2048/page/index.dart'; -import 'package:mimir/game/2048/page/records.dart'; -import 'package:mimir/game/index.dart'; -import 'package:mimir/game/minesweeper/page/index.dart'; -import 'package:mimir/game/page/settings.dart'; -import 'package:mimir/game/sudoku/page/index.dart'; -import 'package:mimir/game/sudoku/page/records.dart'; import 'package:mimir/index.dart'; import 'package:mimir/init.dart'; import 'package:mimir/life/page/settings.dart'; @@ -83,7 +76,6 @@ import 'package:mimir/timetable/page/mine.dart'; import 'package:mimir/timetable/p13n/page/palette.dart'; import 'package:mimir/widget/image.dart'; -import 'game/minesweeper/page/records.dart'; final $TimetableShellKey = GlobalKey(); final $SchoolShellKey = GlobalKey(); @@ -212,10 +204,6 @@ final _lifeShellRoute = GoRoute( path: "/life", builder: (ctx, state) => const LifePage(), ); -final _gameShellRoute = GoRoute( - path: "/game", - builder: (ctx, state) => const GamePage(), -); final _meShellRoute = GoRoute( path: "/me", builder: (ctx, state) => const MePage(), @@ -291,10 +279,6 @@ final _settingsRoute = GoRoute( path: "life", builder: (ctx, state) => const LifeSettingsPage(), ), - GoRoute( - path: "game", - builder: (ctx, state) => const GameSettingsPage(), - ), GoRoute( path: "about", builder: (ctx, state) => const AboutSettingsPage(), @@ -535,53 +519,6 @@ final _webviewRoute = GoRoute( throw 400; }, ); -final _gameRoutes = [ - GoRoute( - path: "/game/2048", - builder: (ctx, state) { - final continueGame = state.uri.queryParameters["continue"] != null; - return Game2048Page(newGame: !continueGame); - }, - routes: [ - GoRoute( - path: "records", - builder: (ctx, state) { - return const Records2048Page(); - }, - ), - ], - ), - GoRoute( - path: "/game/minesweeper", - builder: (ctx, state) { - final continueGame = state.uri.queryParameters["continue"] != null; - return GameMinesweeperPage(newGame: !continueGame); - }, - routes: [ - GoRoute( - path: "records", - builder: (ctx, state) { - return const RecordsMinesweeperPage(); - }, - ), - ], - ), - GoRoute( - path: "/game/sudoku", - builder: (ctx, state) { - final continueGame = state.uri.queryParameters["continue"] != null; - return GameSudokuPage(newGame: !continueGame); - }, - routes: [ - GoRoute( - path: "records", - builder: (ctx, state) { - return const RecordsSudokuPage(); - }, - ), - ], - ), -]; GoRouter buildRouter(ValueNotifier $routingConfig) { return GoRouter.routingConfig( @@ -652,18 +589,6 @@ RoutingConfig buildCommonRoutingConfig() { _timetableShellRoute, ], ), - StatefulShellBranch( - navigatorKey: $GameShellKey, - routes: [ - _gameShellRoute, - ], - ), - // StatefulShellBranch( - // navigatorKey: $ForumShellKey, - // routes: [ - // _forumShellRoute, - // ], - // ), StatefulShellBranch( navigatorKey: $MeShellKey, routes: [ @@ -689,7 +614,6 @@ RoutingConfig buildCommonRoutingConfig() { _teacherEvalRoute, _oaLoginRoute, _imageRoute, - ..._gameRoutes, ], ); } @@ -706,7 +630,6 @@ RoutingConfig buildTimetableFocusRouter() { _timetableShellRoute, ..._timetableRoutes, _schoolShellRoute, - _gameShellRoute, _lifeShellRoute, _meShellRoute, _webviewRoute, @@ -724,7 +647,6 @@ RoutingConfig buildTimetableFocusRouter() { _teacherEvalRoute, _oaLoginRoute, _imageRoute, - ..._gameRoutes, ], ); } diff --git a/lib/settings/page/developer.dart b/lib/settings/page/developer.dart index 446aa1f8c..a24058880 100644 --- a/lib/settings/page/developer.dart +++ b/lib/settings/page/developer.dart @@ -29,7 +29,6 @@ import 'package:mimir/design/adaptive/editor.dart'; import 'package:mimir/design/adaptive/foundation.dart'; import 'package:mimir/design/adaptive/multiplatform.dart'; import 'package:mimir/design/widget/expansion_tile.dart'; -import 'package:mimir/game/widget/party_popper.dart'; import 'package:mimir/init.dart'; import 'package:mimir/l10n/extension.dart'; import 'package:mimir/login/utils.dart'; @@ -152,7 +151,6 @@ class _DeveloperOptionsPageState extends ConsumerState { }, ), ], - buildPartyPopper(), ]), ), ], @@ -160,24 +158,6 @@ class _DeveloperOptionsPageState extends ConsumerState { ); } - Widget buildPartyPopper() { - return ListTile( - leading: "🎉".text(style: context.textTheme.headlineLarge), - title: "Party popper 🎉".text(), - subtitle: "Tap me!".text(), - trailing: Icon(context.icons.rightChevron), - onTap: () { - context.showSheet((ctx) => Scaffold( - body: [ - const VictoryPartyPopper( - pop: true, - ), - ].stack(), - )); - }, - ); - } - Widget buildDevModeToggle() { final on = ref.watch(Dev.$on); return SwitchListTile.adaptive( diff --git a/lib/settings/page/index.dart b/lib/settings/page/index.dart index ceed27f92..f582e7905 100644 --- a/lib/settings/page/index.dart +++ b/lib/settings/page/index.dart @@ -11,7 +11,6 @@ import 'package:mimir/credentials/entity/login_status.dart'; import 'package:mimir/credentials/init.dart'; import 'package:mimir/design/adaptive/dialog.dart'; import 'package:mimir/design/adaptive/multiplatform.dart'; -import 'package:mimir/feature/utils.dart'; import 'package:mimir/lifecycle.dart'; import 'package:mimir/login/i18n.dart'; import 'package:mimir/network/widget/entrance.dart'; @@ -130,13 +129,6 @@ class _SettingsPageState extends ConsumerState { path: "/settings/life", )); } - if (can("game", ref)) { - all.add(PageNavigationTile( - title: i18n.app.navigation.game.text(), - leading: Icon(context.icons.game), - path: "/settings/game", - )); - } all.add(const Divider()); } if (devOn) { diff --git a/lib/settings/settings.dart b/lib/settings/settings.dart index 1d0efeab9..dda73659b 100644 --- a/lib/settings/settings.dart +++ b/lib/settings/settings.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:mimir/agreements/settings.dart'; import 'package:mimir/backend/update/entity/channel.dart'; -import 'package:mimir/game/settings.dart'; import 'package:mimir/utils/hive.dart'; import 'package:mimir/entity/campus.dart'; import 'package:mimir/school/settings.dart'; @@ -35,7 +34,6 @@ class SettingsImpl { late final life = LifeSettings(box); late final timetable = TimetableSettings(box); late final school = SchoolSettings(box); - late final game = GameSettings(box); late final theme = _Theme(box); late final proxy = _Proxy(box); late final update = _Update(box); diff --git a/lib/timetable/widget/focus.dart b/lib/timetable/widget/focus.dart index a280de2c9..dfebca995 100644 --- a/lib/timetable/widget/focus.dart +++ b/lib/timetable/widget/focus.dart @@ -4,7 +4,6 @@ import 'package:mimir/design/adaptive/menu.dart'; import 'package:mimir/design/adaptive/multiplatform.dart'; import 'package:mimir/school/i18n.dart' as $school; import 'package:mimir/life/i18n.dart' as $life; -import 'package:mimir/game/i18n.dart' as $game; import 'package:mimir/me/i18n.dart' as $me; import 'package:mimir/settings/i18n.dart' as $settings; @@ -32,13 +31,6 @@ List buildFocusPopupActions(BuildContext context) { await context.push("/life"); }, ), - PullDownItem( - icon: Icons.videogame_asset_outlined, - title: $game.i18n.navigation, - onTap: () async { - await context.push("/game"); - }, - ), PullDownItem( icon: context.icons.settings, title: $settings.i18n.title, diff --git a/pubspec.yaml b/pubspec.yaml index d6c40c5fc..71d9122dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -210,8 +210,6 @@ flutter: - assets/room_list.json - assets/webview/ - assets/l10n/ - - assets/game/suika/ - - assets/game/wordle/ flutter_intl: enabled: true diff --git a/test/game_test.dart b/test/game_test.dart deleted file mode 100644 index 4b423908f..000000000 --- a/test/game_test.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mimir/game/minesweeper/utils.dart'; -import 'package:mimir/utils/list2d/impl.dart'; -import 'package:sudoku_solver_generator/sudoku_solver_generator.dart'; - -void main() { - group("List2D", () { - test("Test generating", () { - final map = List2D.generate(9, 9, (row, column, index) => (row, column)); - final map2d = map.to2DList(); - for (final row in map2d) { - debugPrint(row.toString()); - } - }); - test("Test subview", () { - final map = List2D.generate(9, 9, (row, column, index) => (row, column)); - final sub = map.subview(rows: 5, columns: 5, rowOffset: 3, columnOffset: 3); - final sub2d = sub.to2DList(); - for (final row in sub2d) { - debugPrint(row.toString()); - } - }); - }); - - group("Minesweeper", () { - test("Test generate symmetric range", () { - assert(generateSymmetricRange(5, 8).toList().equals([-7, -6, -5, 5, 6, 7])); - assert(generateSymmetricRange(0, 8).toList().equals([-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7])); - assert(generateSymmetricRange(0, 0).toList().equals([])); - assert(generateSymmetricRange(0, 1).toList().equals([0])); - assert(generateSymmetricRange(0, 2).toList().equals([-1, 0, 1])); - assert(generateSymmetricRange(10, 10).toList().equals([])); - assert(generateSymmetricRange(9, 10).toList().equals([-9, 9])); - }); - test("Test generateCoord", () { - assert(generateCoord(1).toList().equals([(0, 0)])); - assert(generateCoord(2) - .toList() - .equals([(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 0), (0, 1), (1, -1), (1, 0), (1, 1)])); - assert(generateCoord(3).toList().equals([ - (-2, -2), - (-2, -1), - (-2, 0), - (-2, 1), - (-2, 2), - (-1, -2), - (-1, -1), - (-1, 0), - (-1, 1), - (-1, 2), - (0, -2), - (0, -1), - (0, 0), - (0, 1), - (0, 2), - (1, -2), - (1, -1), - (1, 0), - (1, 1), - (1, 2), - (2, -2), - (2, -1), - (2, 0), - (2, 1), - (2, 2) - ])); - }); - assert(generateCoord(3, startWith: 2).toList().equals([(-2, -2), (-2, 2), (2, -2), (2, 2)])); - assert(generateCoord(5, startWith: 3).toList().equals([ - (-4, -4), - (-4, -3), - (-4, 3), - (-4, 4), - (-3, -4), - (-3, -3), - (-3, 3), - (-3, 4), - (3, -4), - (3, -3), - (3, 3), - (3, 4), - (4, -4), - (4, -3), - (4, 3), - (4, 4) - ])); - }); - - group("Sudoku", () { - test("Test generating", () { - final generator = SudokuGenerator(emptySquares: 18); - debugPrint(generator.newSudoku.toString()); - debugPrint(generator.newSudokuSolved.toString()); - }); - }); -}