Tekeillä tuota rts-peliä varten (toisessa topiikissa) pieni random funktio joka ei turvautuis liukulukuihin mitenkään. Kokeilin periaatetta että lasketaan ensin ihan normaalein keinoin 2048 valmiiksi arvottua lukua väliltä 0-255 ja laitetaan ne tiedostoon. Jokainen luku esiintyy yhtä monta kertaa mutta Delphin omalla randomilla sekoitettuna.
Nyt sitten tulee pientä ongelmaa kun tuota taulukkoa yrittää ottaa käyttöön. Tarkotuksena on että noiden 2048 sekaisin olevan luvun avulla voi arpoa minkä tahansa (positiivisen) kokonaisluvun.
Kun kutsun arpojaa rnd(101) niin saapi tällasta hajontaa:
http://i39.tinypic.com/2cxzf7.png
Huomaa että osa tietyn välein esiintyy selvästi useammin kun muut. Pikasesti testattuna väli rnd(0)..rnd(16) toimii oikein eli tasanen hajonta eikä yksikään luku tee piikkiä ylös tai alas.
Jos jottain vinkkiä tai huomattavaa tulee mieleen? :)
// Funktio function Rnd(const n: integer): integer; var i,sum: integer; begin result:=0; if n<2 then exit; rnd_seed:=rnd_seed mod 2048; sum:=0; repeat result:=result+_rnd[rnd_seed]; sum:=sum+255; rnd_seed:=(rnd_seed+1) mod 2048; until sum>=n; result:=result*n div (sum+1); end; // _rnd[] taulukon Generointi randomize; for i:=0 to 2047 do _rnd[i]:=i mod 256; for k:=1 to 3 do // Sekoitetaan 3 kertaa for i:=0 to 2047 do begin j:=random(2048); temp:=_rnd[i]; _rnd[i]:=_rnd[j]; _rnd[j]:=temp; end;
Miksei se saa käyttää liukulukuja? Pyöristät vaan arvotun summan alaspäin lähimpään kokonaislukuun.
Alkup. viesti oli siis Tuolla.
Kävi ilmi että eri prosessorit laskee liukuluvuilla eri tavalla. Olen melko varma että kielien yleiset randomit turvaa liukulukuihin jollain tapaa.
Erilaisia variaatioita on tullu myös kokeiltua ja funktio kirjotettu uudestaan tyhjästä melko tuloksetta. 200 luvulla voi jo saada aikaan jonkunlaisen "sahanterän" mutta 3000 on jo karua katseltavaa. Pitäskö mun vaan käyttää suosiolla suurempia lukuja taulukossa tai enemmän lukuja? Jotenkin vaan luulen että se on ns. purkkaratkaisu joka kostautuu kun taas etsii 10 kertaa suurempia satunnaislukuja.
Objektien koordinaatit itse on helppo esittää kokonaisluvuilla. 4 tavuun (sama kuin tyyppi single) mahtuu luku jonka koko on 4 miljardia eikä tuon pelin karttakoko tule olemaan muutamaa tuhatta suurempi. Saan siis rauhassa kertoa koordinaatit 0.001:llä piirtovaiheessa ja olettaa että 1000 kokonaislukuna vastaa lukua 1 desimaaleina.
User137 kirjoitti:
Olen melko varma että kielien yleiset randomit turvaa liukulukuihin jollain tapaa.
Ööö... ei? Yksinkertaisimmat toimivat näin
http://en.wikipedia.org/wiki/
Ainakin voit koodata oman random-funktion samalla tavalla, jos epäilyttää.
Onhan käsite siemenluku tuttu?
http://www.delphibasics.co.uk/RTL.asp?Name=RandSeed
http://www.cplusplus.com/reference/clibrary/
No joo, näyttäis että Borland on käyttäny just tuota linearia vanhoista TP ajoista Delphi 7 asti ainakin (ei ihan virallisesta lähteestä). Homma jatkuu kumminkin sillä...
Käsittääkseni jos tuota omaa ideaa olis jatkanu ja päästä helpolla niin niiden lukujen olis tullu olla vähintään yhtä suuria kuin suurin luku mitä sillä generoidaan ja jottei samankaltaisuutta ole liikaa niin useampia jaksoja. Toisinsanoen olis vieny muistia liikaa. Olis kasvattanu seediä aina yhdellä ja skaalannu sillä kohtaa esiintyvän taulukon luvun halutulla arvolla. Toisaalta jos haluaa rajoittaa muistin käyttöä niin käyttäis pienempiä lukuja sillä menetyksellä että osa luvuista jää esiintymättä kun kysytty arvo on suurempi (mikä ei välttämättä haittaa).
Miten kävisi multiply-with-carry (MWC) random-funktio toteutus?
Alla on Micho Durdevichin OS/2 PM-ohjelmointi esimerkistä kopioitu assembly toteutus:
mov eax, 0x4019CF16 mul random_var add eax, random_var[4] mov random_var, eax adc edx, 0 mov random_var[4], edx
Kehuvat hyväksi ja nopeaksi sellaista kuin Mersenne twister. Sivun lopussa on myös pseudokoodi, joka vääntyy Pascaliksi aika näppärästi.
Complimentary-Multiply-With-Carry on aika paljon yksinkertaisempi ja nopeampi kuin Mersenne Twister, mutta silti riittävän satunnainen ja erittäin pitkäjaksoinen.
Ikiomaa algoritmia ei oikein kannata ruveta väsäämään, ellei ole hallussa aikas paljon teoriaa ja tutkimusta. Omaan peliin riittää varmasti kirjaston perus-random. Nettipokerissa tai jossain Monte Carlo -laskennassa on parasta siirtyä käyttämään jotakin ihan totisesti oikeata algoritmitoteutusta, joka on myös testattu oikein.
Alla esimerkkinä aikaisemmin mainitsemani MWC random-funktio OpenWatcom C-kutsuttavana assembler toteutuksena.
rand1.asm
.386p _DATA SEGMENT DWORD PUBLIC USE32 'DATA' public _random_var1 public _random_var2 _random_var1 dd 8 _random_var2 dd 5 _DATA ENDS DGROUP GROUP _DATA _TEXT SEGMENT PARA PUBLIC USE32 'CODE' ASSUME CS:_TEXT, DS:_DATA public Randomize_ Randomize_ proc near push ebx push edx cmp eax, 0 jz @random_exit cmp eax, 0xFFFFFFFF jz @random_exit mov ebx, eax mov eax, 0x4019CF16 mul _random_var1 add eax, _random_var1[4] mov _random_var1, eax adc edx, 0 mov _random_var1[4], edx xor edx, edx inc ebx div ebx xchg edx, eax @random_exit: pop edx pop ebx ret Randomize_ endp _TEXT ends end
Jos haluat testata, niin tee uusi projekti. Määritä alustaksi 32-bittinen DOS (Causeway -tai dos4gw-extender) ja liitä ylläolevan tiedoston lisäksi projektiin allaolevat tiedostot:
buftoscr.asm
.386p extrn _Screen:dword extrn _ScreenBuffer:dword _TEXT SEGMENT PARA PUBLIC USE32 'CODE' ASSUME CS:_TEXT, DS:_DATA public BufferToScreen_ BufferToScreen_ proc near push esi push edi ; Kopioi näyttöpuskurin sisällön näyttömuistiin 32 bittiä kerrallaan!!! ; cld mov edi, _Screen ; osoitin videomuistiin mov esi, _ScreenBuffer ; osoitin puskuriin mov cx,((320*200)/4) ; kopioitavan määrä mov dx,3dah ; VGA portti ja indeksi (Input Status Register) @retrace: in al,dx ; lue VGA portti and al,8 ; selvitä tahdistuksen tila jz @retrace ; odota näytön tahdistusta rep movsd ; kopioi 32-bittiä kerrallaan pop edi pop esi ret BufferToScreen_ endp public ClearBuffer_ ClearBuffer_ proc near push edi cld mov edi, _ScreenBuffer mov eax,0 ; täyttöväri mov cx,((320*200)/4) ; kopioitavan määrä rep stosd ; kopioi 32-bittiä kerrallaan pop edi ret ClearBuffer_ endp _TEXT ends end
main.c
// // OpenWatcom versio spritekääntäjästä // #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <dos.h> #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 200 #define SPRITE_WIDTH 24 #define SPRITE_HEIGHT 16 typedef unsigned char uchar; typedef void near (*CompiledSprite)(uchar *,int,int); CompiledSprite CompileSprite(uchar *pBitmap, int width, int height); CompiledSprite CompileSprite(uchar *pBitmap, int width, int height) { int row, column; int b1, b2, b3, b4; int dwords=0, words=0, bytes=0; unsigned int Offset, BufferSize, Index=0; uchar *CodeBuffer; // Selvitetään kuinka monta tavua kerrallaan voidaan kopioida for(row=0; row < height; row++) { Offset = row * width; for(column=0; column < width; column++) { b1 = (pBitmap[Offset+column] != 0 ) ? 1 : 0; if((column+1) < width) { b2 = (pBitmap[Offset+column+1] != 0 ) ? 1 : 0; if((column+2) < width) { b3 = (pBitmap[Offset+column+2] != 0 ) ? 1 : 0; if((column+3) < width) { b4 = (pBitmap[Offset+column+3] != 0 ) ? 1 : 0; } else b4 = 0; } else b3 = 0; } else b2 = 0; if(b1) { if(b2) { if(b3) { if(b4) { dwords++; column+=3; } else { words++; bytes++; column+=2; } } else { words++; column++; } } else { bytes++; } } } // Lopeta sarake looppi } // Lopeta rivi looppi // Lasketaan tarvittava puskurin koko BufferSize = (10 * dwords) + (9 * words) + (7 * bytes) + 15; // Varataan tilaa koodipuskurille CodeBuffer = (uchar *) malloc(BufferSize); if(!CodeBuffer) return (CompiledSprite)CodeBuffer; // Käydään kuvatieto läpi uudestaan ja kirjoitetaan puskuriin // konekielinen piirtorutiini // Parametrit rekistereistä CodeBuffer[Index++] = 0x89; // mov ecx,ebx CodeBuffer[Index++] = 0xd9; CodeBuffer[Index++] = 0xc1; // shl ebx,08h CodeBuffer[Index++] = 0xe3; CodeBuffer[Index++] = 0x08; CodeBuffer[Index++] = 0xc1; // shl ecx,06h CodeBuffer[Index++] = 0xe1; CodeBuffer[Index++] = 0x06; CodeBuffer[Index++] = 0x01; // add eax,ebx CodeBuffer[Index++] = 0xd8; CodeBuffer[Index++] = 0x01; // add eax,ecx CodeBuffer[Index++] = 0xc8; CodeBuffer[Index++] = 0x01; // add eax,edx CodeBuffer[Index++] = 0xd0; // Kuvatieto for(row=0; row < height; row++) { Offset = row * width; for(column=0; column < width; column++) { b1 = (pBitmap[Offset+column] != 0 ) ? 1 : 0; if((column+1) < width) { b2 = (pBitmap[Offset+column+1] != 0 ) ? 1 : 0; if((column+2) < width) { b3 = (pBitmap[Offset+column+2] != 0 ) ? 1 : 0; if((column+3) < width) { b4 = (pBitmap[Offset+column+3] != 0 ) ? 1 : 0; } else b4 = 0; } else b3 = 0; } else b2 = 0; if(b1) { if(b2) { if(b3) { if(b4) { // mov dword ptr [eax+offset32],xxxxxxxx CodeBuffer[Index++] = 0xc7; CodeBuffer[Index++] = 0x80; // 32-bit offset CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF); CodeBuffer[Index++] = ((row * 320 + column) >> 8); CodeBuffer[Index++] = 0x00; CodeBuffer[Index++] = 0x00; // pixel data CodeBuffer[Index++] = (uchar) pBitmap[Offset + column]; CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+1]; CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+2]; CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+3]; column+=3; } else { // mov word ptr [eax+offset32],xxxx CodeBuffer[Index++] = 0x66; CodeBuffer[Index++] = 0xc7; CodeBuffer[Index++] = 0x80; // 32-bit offset CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF); CodeBuffer[Index++] = ((row * 320 + column) >> 8); CodeBuffer[Index++] = 0x00; CodeBuffer[Index++] = 0x00; // pixel data CodeBuffer[Index++] = (uchar) pBitmap[Offset + column]; CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+1]; // mov byte ptr [eax+offset32],xx CodeBuffer[Index++] = 0xc6; CodeBuffer[Index++] = 0x80; // 32-bit offset CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF); CodeBuffer[Index++] = ((row * 320 + column) >> 8); CodeBuffer[Index++] = 0x00; CodeBuffer[Index++] = 0x00; // pixel data CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+2]; column+=2; } } else { // mov word ptr [eax+offset32],xxxx CodeBuffer[Index++] = 0x66; CodeBuffer[Index++] = 0xc7; CodeBuffer[Index++] = 0x80; // 32-bit offset CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF); CodeBuffer[Index++] = ((row * 320 + column) >> 8); CodeBuffer[Index++] = 0x00; CodeBuffer[Index++] = 0x00; // pixel data CodeBuffer[Index++] = (uchar) pBitmap[Offset + column]; CodeBuffer[Index++] = (uchar) pBitmap[Offset + column+1]; column++; } } else { // mov byte ptr [eax+offset32],xx CodeBuffer[Index++] = 0xc6; CodeBuffer[Index++] = 0x80; // 32-bit offset CodeBuffer[Index++] = ((row * 320 + column) & 0x00FF); CodeBuffer[Index++] = ((row * 320 + column) >> 8); CodeBuffer[Index++] = 0x00; CodeBuffer[Index++] = 0x00; // pixel data CodeBuffer[Index++] = (uchar) pBitmap[Offset + column]; } } } // Lopeta sarake looppi } // Lopeta rivi looppi // paluu CodeBuffer[Index++] = 0xc3; // retn return (CompiledSprite)CodeBuffer; } ///////////////////////////////////////////////////////////////////////////////////////////////// // Seuraava on vain testiä varten ... ///////////////////////////////////////////////////////////////////////////////////////////////// void SetMode13h(void); #pragma aux SetMode13h = \ " mov ax,13h " \ " int 10h " \ modify [ax]; void SetMode03h(void); #pragma aux SetMode03h = \ " mov ax,03h " \ " int 10h " \ modify [ax]; extern int Randomize(int); extern void ClearBuffer(void); extern void BufferToScreen(void); extern int random_var1; extern int random_var2; struct dostime_t time; uchar *Screen; uchar *ScreenBuffer; CompiledSprite testi; uchar Sprite_data[] = { 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00, 00,00,00,00,00,00,16,16,16,16,16,16,16,16,16,16,16,16,16,00,00,00,00,00, 00,00,00,00,16,16,16,14,14,14,14,14,14,14,14,14,14,14,16,16,16,00,00,00, 00,00,00,16,16,14,14,14,16,14,14,14,14,14,14,16,14,14,14,14,16,16,00,00, 00,16,16,16,14,14,14,16,16,16,14,14,14,14,16,16,16,14,14,14,14,16,16,00, 00,16,14,14,14,14,14,16,16,16,14,14,14,14,16,16,16,14,14,14,14,14,16,00, 00,16,14,14,14,14,14,14,16,14,14,14,14,14,14,16,14,14,14,14,14,14,16,00, 00,16,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,16,00, 00,16,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,16,00, 00,16,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,16,00, 00,16,14,14,14,16,14,14,14,14,14,14,14,14,14,14,14,14,16,14,14,14,16,00, 00,16,14,14,14,14,16,14,14,14,14,14,14,14,14,14,14,16,14,14,14,14,16,00, 00,16,16,16,14,14,14,16,16,16,16,16,16,16,16,16,16,14,14,14,16,16,16,00, 00,00,00,16,16,14,14,14,14,14,14,14,14,14,14,14,14,14,14,16,16,00,00,00, 00,00,00,00,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,00,00,00,00, 00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00 }; int main(void) { _dos_gettime( &time ); random_var1 = time.second; random_var2 = time.minute; testi=(CompiledSprite)CompileSprite(Sprite_data, SPRITE_WIDTH, SPRITE_HEIGHT); ScreenBuffer=malloc(SCREEN_WIDTH*SCREEN_HEIGHT); if(!ScreenBuffer) { printf("Ei tarpeeksi muistia näyttöpuskurille\n"); exit(1); } ClearBuffer(); Screen = (uchar *) 0xA0000; // osoitin videomuistiin SetMode13h(); while(!kbhit()) { testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT)); testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT)); testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT)); testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT)); testi(ScreenBuffer, Randomize(SCREEN_WIDTH - SPRITE_WIDTH), Randomize(SCREEN_HEIGHT - SPRITE_HEIGHT)); BufferToScreen(); } free(ScreenBuffer); SetMode03h(); return 0; }
Ohjelman pitäisi piirtää melko vauhdikkaasti hymynaamoja ruudulle satunnaisille paikoille. Ai niin... kyseessä ei sitten ole mikään esimerkki hyvännäköisestä C-ohjelmointi tavasta.
ja alla dailywtf:sta
function random(){ return 4; // luku on valittu täysin satunnaisesti }
Aihe on jo aika vanha, joten et voi enää vastata siihen.